Filament v4 migration – Livewire SupportValidation error and login redirect not working

I am migrating a project and found an issue I can’t resolve.
Here are the details:

Versions after migration
Filament: v4.0 (from v3.2.117)
PHP: 8.3 (from 8.2)
Laravel: 12.26.3 (from 11.44.0)
Livewire: 3.6.4 (from 3.5.6)

Problem
When loading the login route, I get the following error:

local.ERROR: IlluminateSupportViewErrorBag::put(): 
Argument #2 ($bag) must be of type IlluminateContractsSupportMessageBag, null given, 
called in /vendor/livewire/livewire/src/Features/SupportValidation/SupportValidation.php on line 21

Trace points to SupportValidation->render() and ViewErrorBag->put() receiving null instead of a MessageBag.

Temporary workaround
To bypass this, I temporarily patched SupportValidation and SupportTesting in the vendor/ folder so that a new MessageBag instance is created when getErrorBag() is null. Example (simplified):

if (! $bag instanceof MessageBag) {
    $bag = new MessageBag((array) $bag);
    if (method_exists($this->component, 'setErrorBag')) {
        $this->component->setErrorBag($bag);
    }
}

**With this hack:

The login page renders correctly.

Authentication works (confirmed via browser dev tools and Laravel logs).

But the redirect after login does not happen** — the user stays on the login page even though authentication is successful.

What I tried:

  • Verified that the user is authenticated (Auth::check() returns true).

  • Logs confirm login success (tail -f storage/logs/laravel.log).

  • Without patching the vendor files, the login page never renders (due to the null MessageBag error).

  • With patching, page renders but redirect is broken.

Questions

1 – Is this a known issue with Filament v4 + Livewire 3.6.x during login?

2 – Should I be initializing the MessageBag in a different way rather than patching vendor code?

3 – Why would the redirect fail even though authentication succeeds?

Unfortunately, my project is private, but I can try to create a minimal reproduction repo if needed.

Any help or guidance would be appreciated.

Form.io DataGrid custom pager component: Select component value resets when switching pages

I am building a custom DataGrid pager component in Form.io to allow pagination inside grids. The pager works correctly for all components (text fields, checkboxes, etc.), except for Select components (I’m using Angular 19.2.0, @formio/angular: ^9.0.0, formiojs: ^4.21.7)

The issue happens when I add a Select component to a row in the DataGrid and then navigate between pages. The Select value flickers or resets, usually defaulting to the first available option, even though the underlying data is correct.

For example:
DataGrid (pageLimit = 3)

TextField Select
1 A
2 B
3 C
4 D

On Page 1, I see rows 1–3 (A, B, C) correctly.

When I go to Page 2, I expect to see row 4 (D), but instead the TextField shows 4 and the Select shows A (the first option), instead of D.

I confirmed that the data itself is fine by logging allGridRows and the values are stored correctly. The problem is only with the UI rendering of the Select component when switching pages.

So far, I tried debugging by checking how values are passed when re-rendering the grid, but I can’t figure out why the Select component specifically loses its correct value, while other components keep theirs.

I’ve gone through the Form.io source code for the Select component to understand how it handles rendering and value persistence. However, I couldn’t find anything that directly explains why the Select value resets only when used inside my custom DataGrid pager, maybe it’s a skill issue.

Other components (like text fields) render correctly across pages, so this issue seems to be specific to Select.

I also verified that:

  1. The data itself is correct (via console.log(allGridRows)), so the problem isn’t in the storage.
  2. The issue happens only in the UI re-rendering when switching pages.
  3. Manually editing rows (like setting 4 → D) stores the correct value, but the Select resets when navigating back to that page.

This is my code (see: in app.component.ts, submissionData has Dal in the 4th row, but in the table Aban is showing in the 4th row. Try changing the Select component data and moving between pages): StackBlitz Example

Since my custom pager is meant to support multiple component types inside DataGrids, I need Select to behave consistently with other components. Right now, the UI resetting breaks the user experience and makes it seem like data was lost, even though it’s still stored.

I believe the issue comes from how FormIO renders Select component values (specifically how setValue works), rather than from the logic in my DataGridPager implementation (DataGridPager.js):

import { Formio } from "@formio/js";
import editForm from "./DataGridPager.form.js";

const Component = Formio.Components.components.component;

export default class DataGridPager extends Component {
    static editForm = editForm;

    static schema(...extend) {
        return Component.schema(
            {
                type: "dataGridPager",
                label: "Data Pager",
                key: "dataGridPager",
                pageLimit: 5,
                gridToAttach: "",
            },
            ...extend
        );
    }

    static get builderInfo() {
        return {
            title: "Data Grid Pager",
            icon: "list",
            group: "basic",
            weight: -100,
            schema: DataGridPager.schema(),
        };
    }

    constructor(component, options, data) {
        super(component, options, data);

        this.pageLimit =
            Number(this.component.pageLimit) > 0
                ? Number(this.component.pageLimit)
                : 5;
        this.currentPageNum = 1;
        this.totalPagesNum = 1;
        this.totalRowsNum = 1;
        this.allGridRows = [];
        this.targetComponent = null;
    }

    render() {
        return super.render(this.renderTemplate("dataGridPager"));
    }

    /**
     * Called after render; wires refs, finds target component, and initializes UI.
     */
    attach(element) {
        // Let base class attach first
        super.attach(element);

        // Load refs from template
        this.loadRefs(element, {
            dataGridPager: "single",
            firstItemNum: "single",
            lastItemNum: "single",
            totalItemsNum: "single",
            currentPageNum: "single",
            totalPagesNum: "single",
            firstBtn: "single",
            prevBtn: "single",
            nextBtn: "single",
            lastBtn: "single",
        });

        // Locate the grid/etc to attach to
        this.targetComponent = this.findTargetComponent();

        // If not found, show zero state and disable buttons
        if (!this.targetComponent) {
            this.allGridRows = [];
            this.computeTotals();
            this.updateUI();
            this.setButtonsDisabled(true);
            return;
        }

        // Verify it's actually a data grid component
        if (this.targetComponent.component.type !== "datagrid") {
            console.warn(
                `DataGridPager: Component "${this.component.gridToAttach}" is not a data grid`
            );
            this.refs.dataGridPager.innerHTML = `<div>Error: Attached component is not a data grid</div>`;
            this.allGridRows = [];
            this.computeTotals();
            this.updateUI();
            this.setButtonsDisabled(true);
            return;
        }

        // Compute totals and clamp current page
        this.computeTotals();

        // Wire button click handlers (store references for cleanup)
        this._firstHandler = () => this.goToPage(1);
        this._prevHandler = () => this.goToPage(this.currentPageNum - 1);
        this._nextHandler = () => this.goToPage(this.currentPageNum + 1);
        this._lastHandler = () => this.goToPage(this.totalPagesNum);

        if (this.refs.firstBtn)
            this.refs.firstBtn.addEventListener("click", this._firstHandler);
        if (this.refs.prevBtn)
            this.refs.prevBtn.addEventListener("click", this._prevHandler);
        if (this.refs.nextBtn)
            this.refs.nextBtn.addEventListener("click", this._nextHandler);
        if (this.refs.lastBtn)
            this.refs.lastBtn.addEventListener("click", this._lastHandler);

        // Listen for changes inside the target component so edits persist across pages.
        // Not all component implementations expose on/off; guard those calls.
        this._targetChangeHandler = () => {
            // read the current visible page rows from the target and merge them back
            const currentPageData = Array.isArray(this.targetComponent.dataValue)
                ? JSON.parse(JSON.stringify(this.targetComponent.dataValue))
                : [];

            const offset = (this.currentPageNum - 1) * this.pageLimit;
            // Remove the old slice and replace with new
            this.allGridRows.splice(offset, this.pageLimit, ...currentPageData);

            // Recompute totals in case rows were added/removed
            this.computeTotals();

            // If this page exceeds the limit, move to next page
            if (currentPageData.length > this.pageLimit) {
                this.goToPage(this.currentPageNum + 1);
            } else {
                // Otherwise just refresh this page
                this.goToPage(this.currentPageNum);
            }
        };

        this._targetDeleteHandler = () => {
            // read the current visible page rows from the target and merge them back
            const currentPageData = Array.isArray(this.targetComponent.dataValue)
                ? JSON.parse(JSON.stringify(this.targetComponent.dataValue))
                : [];

            if (currentPageData.length === 0 && this.currentPageNum > 1) {
                // If we deleted the last item on this page, move back a page
                this.currentPageNum--;
                this.allGridRows.pop();
            } else {
                const offset = (this.currentPageNum - 1) * this.pageLimit;
                // Remove the old slice and replace with new
                this.allGridRows.splice(offset, this.pageLimit, ...currentPageData);
            }

            // Recompute totals in case rows were added/removed
            this.computeTotals();

            this.goToPage(this.currentPageNum);
        };

        if (typeof this.targetComponent.on === "function") {
            this.targetComponent.on("change", this._targetChangeHandler);
            this.targetComponent.on("dataGridDeleteRow", this._targetDeleteHandler);
        } else if (typeof this.targetComponent.addEventListener === "function") {
            // fallback (rare)
            this.targetComponent.addEventListener(
                "change",
                this._targetChangeHandler
            );
            this.targetComponent.addEventListener(
                "dataGridDeleteRow",
                this._targetDeleteHandler
            );
        }

        // Show initial page
        this.goToPage(this.currentPageNum);
    }

    /**
     * Find the target component by key (searches recursively from root)
     */
    findTargetComponent() {
        if (!this.root || !this.component?.gridToAttach) return null;
        try {
            return this.root.getComponent(this.component.gridToAttach);
        } catch (e) {
            return null;
        }
    }

    /**
     * Recalculate totals and clamp current page
     */
    computeTotals() {
        this.totalRowsNum = Array.isArray(this.allGridRows)
            ? this.allGridRows.length
            : 1;
        this.totalPagesNum = Math.max(
            1,
            Math.ceil(this.totalRowsNum / this.pageLimit)
        );
        if (this.currentPageNum < 1) this.currentPageNum = 1;
        if (this.currentPageNum > this.totalPagesNum)
            this.currentPageNum = this.totalPagesNum;
    }

    /**
     * Show a specific page: slice items, set target value, update UI
     */
    goToPage(pageNum) {
        if (!this.targetComponent) return;

        // ensure pageNum is always between 1 and totalPagesNum
        const currentPageNum = Math.max(
            1,
            Math.min(pageNum || 1, this.totalPagesNum)
        );
        this.currentPageNum = currentPageNum;

        const start = (currentPageNum - 1) * this.pageLimit;
        const end = start + this.pageLimit;
        const pageSlice = this.allGridRows.slice(start, end);

        // Set the visible rows in the target component.
        // setValue should exist on formio components; call defensively.
        if (typeof this.targetComponent.setValue === "function") {
            this.targetComponent.setValue(pageSlice);
        } else {
            // fallback: set dataValue directly (less ideal)
            this.targetComponent.dataValue = pageSlice;
        }

        // Update UI text and button states
        this.updateUI();
    }

    /**
     * Update the DOM refs for numbers and button disabled states
     */
    updateUI() {
        // First/last item numbers: show 1 when no items
        const firstNum =
            this.totalRowsNum === 1
                ? 1
                : (this.currentPageNum - 1) * this.pageLimit + 1;
        const lastNum =
            this.totalRowsNum === 1
                ? 1
                : Math.min(this.currentPageNum * this.pageLimit, this.totalRowsNum);

        if (this.refs.firstItemNum) this.refs.firstItemNum.innerText = firstNum;
        if (this.refs.lastItemNum)
            this.refs.lastItemNum.innerText = lastNum > 0 ? lastNum : 1;
        if (this.refs.totalItemsNum)
            this.refs.totalItemsNum.innerText =
                this.totalRowsNum > 0 ? this.totalRowsNum : 1;
        if (this.refs.currentPageNum)
            this.refs.currentPageNum.innerText =
                this.totalRowsNum === 1 ? 1 : this.currentPageNum;
        if (this.refs.totalPagesNum)
            this.refs.totalPagesNum.innerText = this.totalPagesNum;

        // Button enable/disable
        const onFirst = this.totalRowsNum === 1 || this.currentPageNum === 1;
        const onLast =
            this.totalRowsNum === 1 || this.currentPageNum === this.totalPagesNum;

        if (this.refs.firstBtn) this.refs.firstBtn.disabled = onFirst;
        if (this.refs.prevBtn) this.refs.prevBtn.disabled = onFirst;
        if (this.refs.nextBtn) this.refs.nextBtn.disabled = onLast;
        if (this.refs.lastBtn) this.refs.lastBtn.disabled = onLast;
    }

    /**
     * enable/disable all buttons quickly
     */
    setButtonsDisabled(disabled = true) {
        if (this.refs.firstBtn) this.refs.firstBtn.disabled = disabled;
        if (this.refs.prevBtn) this.refs.prevBtn.disabled = disabled;
        if (this.refs.nextBtn) this.refs.nextBtn.disabled = disabled;
        if (this.refs.lastBtn) this.refs.lastBtn.disabled = disabled;
    }

    /**
     * Returns the full dataset (all pages) and the target component key
     * for external use (e.g. on form submit)
     */
    getAllGridRows() {
        return [this.allGridRows, this.targetComponent.key];
    }

    /**
     * Cleanup listeners when component is removed
     */
    detach() {
        // remove button listeners
        if (this.refs.firstBtn && this._firstHandler)
            this.refs.firstBtn.removeEventListener("click", this._firstHandler);
        if (this.refs.prevBtn && this._prevHandler)
            this.refs.prevBtn.removeEventListener("click", this._prevHandler);
        if (this.refs.nextBtn && this._nextHandler)
            this.refs.nextBtn.removeEventListener("click", this._nextHandler);
        if (this.refs.lastBtn && this._lastHandler)
            this.refs.lastBtn.removeEventListener("click", this._lastHandler);

        // remove target change listener
        if (this.targetComponent) {
            if (typeof this.targetComponent.off === "function") {
                if (this._targetChangeHandler)
                    this.targetComponent.off("change", this._targetChangeHandler);
                if (this._targetDeleteHandler)
                    this.targetComponent.off(
                        "dataGridDeleteRow",
                        this._targetDeleteHandler
                    );
            } else if (
                typeof this.targetComponent.removeEventListener === "function"
            ) {
                if (this._targetChangeHandler)
                    this.targetComponent.removeEventListener(
                        "change",
                        this._targetChangeHandler
                    );

                if (this._targetDeleteHandler)
                    this.targetComponent.removeEventListener(
                        "dataGridDeleteRow",
                        this._targetDeleteHandler
                    );
            }
        }

        return super.detach();
    }
}

why do my forEach results change every time the page loads

I want to dynamically create list elements. I used forEach and I console logged the ul but the results aren’t consistent in the console when I reload the page. They are consistent in the UI. But in the console sometimes it shows ”. I can click on it and see the 7 list elements and their ids. Other times it shows ‘ul#genre-list”. I can click on it and it lists info such as an access key, attributes, childNodes, children, etc. I’m not sure what it’s called. But why is it inconsistent?

JS

albums = [
  {
    id: 1,
    genre: 'Rock',
    image1: '/images/album-covers/jimihindrix-areyouexperienced.jpg',
    image2: '/images/album-covers/thedoors.jpg',
  },
  {
    id: 2,
    genre: 'Jazz',
    image1: '/images/album-covers/somethinelse.jpg',
    image2: '/images/album-covers/MilesDavis-BitchesBrew.jpg',
  },
  {
    id: 3,
    genre: 'Hip-Hop',
    image1: '/images/album-covers/mfdoom-mmfood.jpg',
    image2: '/images/album-covers/nas-illmatic.jpg',
  },
  {
    id: 4,
    genre: 'Soul',
    image1: '/images/album-covers/aliciakeys-diary.jpeg',
    image2: '/images/album-covers/ettajames-tellmama.jpg',
  },
  {
    id: 5,
    genre: 'Electronic',
    image1: '/images/album-covers/burial-tunes.jpg',
    image2: '/images/album-covers/björk-homogenic.jpg',
  },
  {
    id: 6,
    genre: 'Classic',
    image1: '/images/album-covers/sparks-kimonomyhouse.jpg',
    image2: '/images/album-covers/chakakhan-rufus.jpg',
  },

  {
    id: 7,
    genre: 'Pop',
    image1: '/images/album-covers/beatles-sgtpeppers.jpg',
    image2: '/images/album-covers/prince-purplerain.png.webp',
  },
];

const genreList = document.getElementById('genre-list');

const handleClick = () => {
  albums.forEach((album) => {
    const content = `<li id=${album.genre}>${album.genre}</li>`;
    genreList.innerHTML += content;
    console.log(genreList);
  });
};

document.addEventListener('DOMContentLoaded', () => {
  handleClick();
});

HTML

<ul id="genre-list"></ul>

Plotly R: Annotations showing fixed X values on hover despite recalculating closest points across subplots

I’m working with a Plotly figure in R that has 3 subplots sharing the same Y-axis (using subplot() with shareY = TRUE). I’ve implemented custom hover behavior using onRender() that:

  1. Draws a horizontal dashed line across all subplots at the cursor’s Y position
  2. For each subplot, finds the closest point to the hovered Y value
  3. Displays an annotation showing the X and Y values of that closest point

The Issue: The Y values in the annotations update correctly as I move the cursor, but the x values don’t change.

What I Want: When hovering over any subplot, I want to see 3 annotations (one per subplot), each showing:

  • The X value of the closest point in that specific subplot
  • The Y value at the cursor position

Both values should update dynamically as the cursor moves.

What I’m Getting:

  • Y values update correctly ✓
  • Annotations move to correct positions ✓
  • X values stay fixed at the initial hover value ✗

Reproducible Example

library(plotly)
library(htmlwidgets)

set.seed(123)
mk_day <- function(n) {
  base <- as.POSIXct(Sys.Date(), tz = "UTC")
  t <- base + sort(sample(0:(24*60*60-1), n))
  data.frame(time = t, value = rnorm(n))
}
df1 <- mk_day(60)
df2 <- transform(mk_day(80), value = value * 2 + 3)
df3 <- transform(mk_day(40), value = value * 3 + 6)

p1 <- plot_ly(df1, x = ~value, y = ~time, type = "scatter", mode = "lines+markers", name = "Serie 1")

p2 <- plot_ly(df2, x = ~value, y = ~time, type = "scatter", mode = "lines+markers", name = "Serie 2")

p3 <- plot_ly(df3, x = ~value, y = ~time, type = "scatter", mode = "lines+markers", name = "Serie 3")

fig <- subplot(p1, p2, p3, nrows = 1, shareY = TRUE, titleX = TRUE, titleY = TRUE) |>
  layout(
    hovermode = "y",
    yaxis = list(type = "date", tickformat = "%H:%M", title = "Hora (24h)"),
    yaxis2 = list(type = "date", tickformat = "%H:%M"),
    yaxis3 = list(type = "date", tickformat = "%H:%M"),
    margin = list(t = 60, r = 10, b = 40, l = 60)
  )


fig_fixed <- onRender(fig, "
function(el, x) {
  var gd = document.getElementById(el.id);

  // Map subplots by X-axis only; Y is shared
  var xAxes = ['x', 'x2', 'x3'];

  // Helper: get x-axis name for a trace (e.g., 'x', 'x2', 'x3')
  function xAxisOfTrace(tr) {
    if (tr && typeof tr.xaxis === 'string') return tr.xaxis; // 'x', 'x2', 'x3'
    return 'x'; // fallback
  }

  gd.on('plotly_hover', function(evt) {
    if (!evt || !evt.points || !evt.points.length) return;

    // 1) Y value under the cursor (shared Y coordinates)
    var yHover = evt.points[0].y;

    // 2) Single horizontal line across ALL subplots (shared Y)
    var lineShape = [{
      type: 'line',
      xref: 'paper', x0: 0, x1: 1,
      yref: 'y',     y0: yHover, y1: yHover,
      line: { width: 2, dash: 'dot' }
    }];

    // 3) For each subplot (x, x2, x3), find the x whose y is closest to yHover
    //    We compute ONE annotation per subplot.
    var bestByXAxis = {}; // e.g., { x: {x:..., y:..., diff:...}, x2: {...}, x3: {...} }

    for (var i = 0; i < gd.data.length; i++) {
      var tr = gd.data[i];
      if (!tr || !tr.x || !tr.y) continue;
      if (!Array.isArray(tr.x) || !Array.isArray(tr.y) || tr.x.length !== tr.y.length) continue;

      var xa = xAxisOfTrace(tr); // 'x' | 'x2' | 'x3'

      // Find nearest y
      var bestIdx = 0, bestDiff = Infinity;
      for (var k = 0; k < tr.y.length; k++) {
        var d = Math.abs(tr.y[k] - yHover);
        if (d < bestDiff) { bestDiff = d; bestIdx = k; }
      }

      var pick = { x: tr.x[bestIdx], y: tr.y[bestIdx], diff: bestDiff };
      if (!bestByXAxis[xa] || pick.diff < bestByXAxis[xa].diff) {
        bestByXAxis[xa] = pick;
      }
    }

    // 4) Build dynamic annotations (always yref: 'y' because Y is shared)
    var dynAnnotations = [];
    xAxes.forEach(function(xa) {
      var pick = bestByXAxis[xa];
      if (!pick) return;

      // If x are dates (POSIXct), text may need formatting on the R side; here we leave raw.
      var txt = 'x = ' + pick.x + '<br>y ≈ ' + yHover;

      dynAnnotations.push({
        xref: xa,     // 'x' | 'x2' | 'x3'
        yref: 'y',    // shared Y axis
        x: pick.x,
        y: yHover,    // lock onto the horizontal guide
        text: txt,
        showarrow: true,
        arrowhead: 2,
        ax: 20, ay: -20,
        bgcolor: 'rgba(255,255,255,0.85)',
        bordercolor: '#333',
        borderwidth: 1,
        font: { size: 12, color: '#111' },
        align: 'left',
        name: xa + '_' + Math.random() // Fuerza actualización

      });
    });

    // 5) Replace shapes & annotations (do NOT merge with any base annotations)
Plotly.relayout(gd, { shapes: lineShape, annotations: [] }).then(function() {
  Plotly.relayout(gd, { annotations: dynAnnotations });
});
  });

  gd.on('plotly_unhover', function() {
    // Clear all dynamic overlays
    Plotly.relayout(gd, { shapes: [], annotations: [] });
  });
}
")


fig_fixed

Mineflayer assistance with block interactions

I am trying to make a Minecraft mineflayer bot that loads ender pearls in pearl stasis chambers by making it interact with a trapdoor at coordinates that are specified in a postgres database. I have tried a lot but it doesnt change the state of the trapdoor. here is my code. Any suggestions?

import mineflayer from 'mineflayer';
import pathfinderPkg from 'mineflayer-pathfinder';
const { pathfinder, Movements, goals } = pathfinderPkg;

import pg from 'pg';
import fetch from 'node-fetch';
import { Vec3 } from 'vec3';
import mcData from 'minecraft-data';

const { Client } = pg;
const { GoalNear } = goals;

const BOT_CONFIG = {
    host: 'localhost',
    port: 25565,
    username: '33_x',
    auth: 'microsoft',
    version: '1.21.4',
    dbConfig: {
        user: 'postgres',
        host: 'localhost',
        database: 'remotepearl',
        password: 'postgres',
        port: 5432,
    },
    tableName: 'accounts',
    columnUUID: 'uuid',
    columnX: 'x',
    columnY: 'y',
    columnZ: 'z',
    columnWorld: 'world',
    TPLookupCommand: '?tp',
    ActivationRetryCount: 3,
    ActivationRetryDelay: 1500,
    TrapdoorSearchRadius: 3,
    AntiAFKSneakInterval: 10000,
    AntiAFKSneakDuration: 500,
};

const taskQueue = [];
let isProcessingTask = false;

function addTask(task) {
    taskQueue.push(task);
    processQueue();
}

async function processQueue() {
    if (isProcessingTask || taskQueue.length === 0) return;
    isProcessingTask = true;
    const task = taskQueue.shift();
    try {
        await task();
    } catch (error) {
        console.error('Task failed:', error);
    } finally {
        isProcessingTask = false;
        processQueue();
    }
}

async function getUuidFromUsername(username) {
    try {
        const response = await fetch(`https://api.mojang.com/users/profiles/minecraft/${username}`);
        if (!response.ok) return null;
        const data = await response.json();
        return data?.id;
    } catch {
        return null;
    }
}

async function retryOperation(func, maxRetries, delayMs) {
    for (let i = 0; i < maxRetries; i++) {
        try {
            return await func();
        } catch (error) {
            if (i < maxRetries - 1) {
                await new Promise(resolve => setTimeout(resolve, delayMs));
            } else {
                throw error;
            }
        }
    }
}

function getNearbyTrapdoor(center, range) {
    for (let dx = -range; dx <= range; dx++) {
        for (let dy = -range; dy <= range; dy++) {
            for (let dz = -range; dz <= range; dz++) {
                const pos = center.offset(dx, dy, dz);
                const block = bot.blockAt(pos);
                if (block && block.name.includes('trapdoor')) return block;
            }
        }
    }
    return null;
}

async function safeActivateBlock(blockToClick, targetName) {
    const targetPoint = blockToClick.position.offset(0.5, 0.5, 0.5);
    for (let i = 0; i < 2; i++) {
        try {
            const dist = bot.entity.position.distanceTo(targetPoint);
            if (dist > 2) {
                await bot.pathfinder.goto(new GoalNear(blockToClick.position.x, blockToClick.position.y, blockToClick.position.z, 1));
            }
            const facePoint = blockToClick.position.offset(0.5, 0.5, 0);
            await bot.lookAt(facePoint, true);
            await bot.look(0, bot.entity.pitch, true);
            await bot.waitForTicks(2);
            bot.setControlState('sneak', true);
            await bot.waitForTicks(2);
            bot.activateItem();
            await bot.waitForTicks(2);
            bot.activateItem();
            await bot.waitForTicks(2);
            bot.setControlState('sneak', false);
            const newBlock = bot.blockAt(blockToClick.position);
            if (newBlock && newBlock.name.includes('trapdoor')) return;
            throw new Error('Trapdoor activation failed');
        } catch (e) {
            if (i < 1) await bot.waitForTicks(10);
            else throw e;
        } finally {
            bot.setControlState('sneak', false);
        }
    }
}

const bot = mineflayer.createBot(BOT_CONFIG);
bot.loadPlugin(pathfinder);

bot.on('spawn', () => {
    const data = mcData(bot.version);
    const defaultMove = new Movements(bot, data);
    bot.pathfinder.setMovements(defaultMove);
    startAntiAFK();
});

bot.on('error', console.error);
bot.on('kicked', console.error);

bot.on('chat', (username, message) => {
    if (username === bot.username) return;
    const args = message.trim().split(' ');
    const command = args[0];
    const targetPlayer = args[1];
    if (command === BOT_CONFIG.TPLookupCommand && targetPlayer) {
        addTask(() => handleTpCommand(username, targetPlayer));
    }
});

function startAntiAFK() {
    setInterval(() => {
        if (!isProcessingTask) {
            bot.setControlState('sneak', true);
            setTimeout(() => bot.setControlState('sneak', false), BOT_CONFIG.AntiAFKSneakDuration);
        }
    }, BOT_CONFIG.AntiAFKSneakInterval);
}

async function handleTpCommand(requesterName, targetName) {
    const dbClient = new Client(BOT_CONFIG.dbConfig);
    try {
        const uuid = await getUuidFromUsername(targetName);
        if (!uuid) return;
        await dbClient.connect();
        const dbResult = await dbClient.query(
            `SELECT "${BOT_CONFIG.columnX}", "${BOT_CONFIG.columnY}", "${BOT_CONFIG.columnZ}", "${BOT_CONFIG.columnWorld}" 
             FROM ${BOT_CONFIG.tableName} 
             WHERE "${BOT_CONFIG.columnUUID}" = $1`,
            [uuid]
        );
        if (dbResult.rows.length === 0) return;
        const { x, y, z, world } = dbResult.rows[0];
        const dbCenterPos = new Vec3(x, y, z);
        const targetDimension = world.toLowerCase().replace('minecraft:', '');
        if (!bot.game.dimension.includes(targetDimension)) return;
        const blockToClick = getNearbyTrapdoor(dbCenterPos, BOT_CONFIG.TrapdoorSearchRadius);
        if (!blockToClick) throw new Error('Trapdoor not found');
        const trapdoorPos = blockToClick.position;
        const botStandPos = trapdoorPos.offset(0, 0, 1);
        const pathfindingGoal = new GoalNear(botStandPos.x, botStandPos.y, botStandPos.z, 0);
        bot.clearControlStates();
        try {
            await bot.pathfinder.goto(pathfindingGoal);
        } catch {
            if (bot.entity.position.distanceTo(botStandPos.offset(0.5, 0.5, 0.5)) > 1.5) throw new Error('Pathfinding failed');
        }
        bot.pathfinder.stop();
        bot.clearControlStates();
        await bot.waitForTicks(2);
        await safeActivateBlock(blockToClick, targetName);
    } catch (error) {
        bot.pathfinder.stop();
        bot.clearControlStates();
        bot.setControlState('sneak', false);
        console.error(error.message);
    } finally {
        if (dbClient) await dbClient.end();
    }
}

I tried changing the way it interacted but nothing works. if it works well it should interact with a specific block and make that block change states, thew block being a trapdoor that it should clode, thus making the ender pearl despawn and teleporting the player.

I get a “Server error. Please try again later” error when calling the Gmail API

I’m building a web app using Google Apps Script and JS. I’ve set up the app to work with OAuth, and it does, so the user is asked for permission to send emails from their account. I’m using the Gmail API to send emails. The problem is that when I run the function from the editor, it works. However, when I run the full program from JS, it returns a server error: try again later.

This is my function

    function enviarCorreoGmailAPISin(to, subject, body) {
 

  const service = getOAuthService();
  if (!service.hasAccess()) throw new Error("No autorizado.");

  const url = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send";
  const correo = [
    "To: " + to,
    "Subject: " + subject,
    "Content-Type: text/html; charset=UTF-8",
    "",
    body
  ].join("rn");

  const payload = {
    raw: Utilities.base64EncodeWebSafe(correo)
  };

  const options = {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(payload),
    headers: {
      Authorization: "Bearer " + service.getAccessToken()
    },
    muteHttpExceptions: true
  };

  const response = UrlFetchApp.fetch(url, options);
  Logger.log(response.getContentText());
}

Copy and Cut Selected Text using Javascript and Paste, all 3 functions on button clicks

I am working on an application where I need Copy, Cut and Paste functionalities using buttons clicks on selected text using Javascript or jQuery/plugin.

I have spent about a day researching on the net but unable to find a solution that works.

For example I tried this:

<textarea name="email-details" id="clickable-input" rows="15" style="width: 100%">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. 
</textarea>

<a href="#" data-copy-id="clickable-input" class="js-click-to-copy-link button">Copy</a><br>

<input value="Text to copy" onclick="this.focus();this.select();document.execCommand('copy')" onfocus="this.focus();this.select();document.execCommand('copy')">

But it works on the whole textarea box.
I also need to copy selected text in to a JS variable for procesing.

Please help, thank you!

How to programmatically insert text into a Slate.js contenteditable so it registers as user input?

I am automating part of a workflow that requires entering text into a job-posting editor. The editor itself is a contenteditable region powered by Slate.js (the DOM shows role=”textbox” and data-slate-editor=”true”). The automation tool I am using is PixieBrix, which allows building in-browser workflow automations via modular “bricks” (JavaScript/TypeScript content scripts packaged into mods).

What we need
A method or best practice to programmatically insert text into a Slate.js editor (or similar rich text editors) so that the host application recognizes it as real input — just as if a human user had typed or pasted the text.

Questions
In environments where the text field is a contenteditable (such as Slate.js), what approaches have you found effective for programmatically entering text so that the application fully registers it?
Are there recommended techniques, tools, or patterns (PixieBrix-specific or more general, e.g. JavaScript/automation libraries) that could help us achieve this reliably?

What we’re trying to achieve
Insert text (fetched from Google Sheets) into this Slate.js editor so that the application recognizes it exactly as if a human had typed or pasted it. For example: if we insert five characters, the editor should show five characters, and the character counter/validations should update accordingly.

What we’ve tried
Using PixieBrix to click into the editor (two click steps: one to focus the editor, one to enter the editable area).
Using the “Set Input Value” brick with the selector: #main [data-ui=’native-texteditor-description’] [role=’textbox’][data-slate-editor=’true’]
Observed behavior
The text appears in the DOM, but the application does not register it. The character counter stays at zero and the editor behaves as if nothing was typed.

Can’t change boolean from inside of .onError in JS [duplicate]

I can’t seem to switch the boolean value of ‘errorcheck’ using image .onerror function.
I’m able to see console logs, trigger functions from inside the .onerror but I just can’t change the variable ‘errorcheck’

The goal is to have a for loop that adds item icons until a broken image path is found

let errorcheck= false;

document.addEventListener('DOMContentLoaded', function() {
  const itemInventory = document.getElementById('itembox');
  const totalItems= 25; //+1 actual count
  let inventoryadd="";
  
  for(let i = 1; i < totalItems; i++) {
    if (errorcheck) {
      break;
    }

    const itemImg= new Image();
    itemImg.src = `images/itemicon${i}.png`;

    // This isn't working
    itemImg.onerror = () => {
      errorcheck= true;
    }

    // returns errorcheck= false;
    console.log(errorcheck);

    inventoryadd += 
    `<li class="itemIcons">
    <img id="itemicon${i}" class="icons" onclick="itemCLick(this.id)" src="images/itemicon${i}.png">
    </li>`;

    myImage.remove();


    }
 
 
  itemInventory.innerHTML += inventoryadd;
    
});

Is there a way to extract the header HTML tag from a given URL dinamically with php?

I have a WordPress website, and I need to extract into a PHP plugin the header tag from this website, and format the code like this:

<style>
    Code stylization grouped if needed on the header here
</style>
<header>
    Header extracted from WordPress
</header>
<script>
    Scripts grouped if needed on the header here
</script>

I already did something interesting, but I’m having trouble loading two dropdown lists with options from the html (HOTELS and LANG).

Here is a sample HTML code from a random page:

<header class="fusion-header-wrapper">
    <div class="fusion-header-v5 fusion-logo-alignment fusion-logo-center fusion-sticky-menu- fusion-sticky-logo- fusion-mobile-logo- fusion-sticky-menu-only fusion-header-menu-align-center fusion-mobile-menu-design-modern">
        <div class="fusion-secondary-header">
            <div class="fusion-row">
                <div class="fusion-alignleft">
                    <div class="fusion-contact-info">
                        <span class="fusion-contact-info-phone-number">(+351) 300 528 059</span>
                        <span class="fusion-header-separator">|</span>
                        <span class="fusion-contact-info-email-address">
                            <a href="mailto:&#114;e&#115;&#101;r&#118;as&#64;tu&#114;&#105;mho&#116;e&#105;&#115;.c&#111;&#109;">&#114;e &#115;&#101;r &#118;as &#64;tu &#114;&#105;mho &#116;e &#105;&#115;.c &#111;&#109;</a>
                        </span>
                    </div>
                </div>
            </div>
        </div>
        <div class="fusion-header-sticky-height"></div>
        <div class="fusion-sticky-header-wrapper">
            <!-- start fusion sticky header wrapper -->
            <div class="fusion-header">
                <div class="fusion-row">
                    <div class="fusion-logo" data-margin-top="31px" data-margin-bottom="31px" data-margin-left="0px" data-margin-right="0px">
                        <a class="fusion-logo-link" href="https://my-site.com/">
                            <!-- standard logo -->
                            <img src="https://my-site.com/Logo.png" srcset="https://my-site.com/Logo.png 1x" width="308" height="105" alt="Book your stay on the Hotels official website! Logo" data-retina_logo_url="" class="fusion-standard-logo"/>
                        </a>
                    </div>
                    <div class="fusion-mobile-menu-icons">
                        <a href="#" class="fusion-icon awb-icon-bars" aria-label="Toggle mobile menu" aria-expanded="false"></a>
                    </div>
                </div>
            </div>
            <div class="fusion-secondary-main-menu">
                <div class="fusion-row">
                    <nav class="fusion-main-menu" aria-label="Main Menu">
                        <div class="fusion-overlay-search">
                            <form role="search" class="searchform fusion-search-form  fusion-search-form-clean" method="get" action="https://my-site.com">
                                <div class="fusion-search-form-content">
                                    <div class="fusion-search-field search-field">
                                        <label>
                                            <span class="screen-reader-text">Search for:</span>
                                            <input type="search" value="" name="s" class="s" placeholder="Search..." required aria-required="true" aria-label="Search..."/>
                                        </label>
                                    </div>
                                    <div class="fusion-search-button search-button">
                                        <input type="submit" class="fusion-search-submit searchsubmit" aria-label="Search" value="&#xf002;"/>
                                    </div>
                                </div>
                            </form>
                            <div class="fusion-search-spacer"></div>
                            <a href="#" role="button" aria-label="Close Search" class="fusion-close-search"></a>
                        </div>
                        <ul id="menu-main-menu" class="fusion-menu">
                            <li id="menu-item-3946" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-3946 fusion-dropdown-menu" data-item-id="3946">
                                <a href="#" class="fusion-bottombar-highlight">
                                    <span class="menu-text">HOTELS</span>
                                    <span class="fusion-caret">
                                        <i class="fusion-dropdown-indicator" aria-hidden="true"></i>
                                    </span>
                                </a>
                                <ul class="sub-menu">
                                    <li id="menu-item-3947" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-3947 fusion-dropdown-submenu">
                                        <a href="#" class="fusion-bottombar-highlight">
                                            <span>HOTEL-01</span>
                                            <span class="fusion-caret">
                                                <i class="fusion-dropdown-indicator" aria-hidden="true"></i>
                                            </span>
                                        </a>
                                        <ul class="sub-menu">
                                            <li id="menu-item-4037" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-4037">
                                                <a href="https://my-site.com/hotel01" class="fusion-bottombar-highlight">
                                                    <span>hotel01</span>
                                                </a>
                                            </li>
                                            <li id="menu-item-4035" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-4035">
                                                <a href="https://my-site.com/hotel02" class="fusion-bottombar-highlight">
                                                    <span>hotel02</span>
                                                </a>
                                            </li>
                                            <li id="menu-item-4782" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-4782">
                                                <a href="https://my-site.com/hotel03" class="fusion-bottombar-highlight fusion-has-highlight-label">
                                                    <span>hotel03</span>
                                                </a>
                                            </li>
                                        </ul>
                                    </li>
                                    <li id="menu-item-3962" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-3962 fusion-dropdown-submenu">
                                        <a href="#" class="fusion-bottombar-highlight">
                                            <span>HOTEL-02</span>
                                            <span class="fusion-caret">
                                                <i class="fusion-dropdown-indicator" aria-hidden="true"></i>
                                            </span>
                                        </a>
                                        <ul class="sub-menu">
                                            <li id="menu-item-4031" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-4031">
                                                <a href="https://my-site.com/hotel01" class="fusion-bottombar-highlight">
                                                    <span>hotel01</span>
                                                </a>
                                            </li>
                                        </ul>
                                    </li>
                                </ul>
                            </li>
                            <li id="menu-item-26285" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-26285" data-item-id="26285">
                                <a href="https://bookings.turim-hotels.com/pt/bookcore/loyalty/account" class="fusion-bottombar-highlight">
                                    <span class="menu-text">CLUB</span>
                                </a>
                            </li>
                            <li id="menu-item-6094" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-6094" data-item-id="6094">
                                <a href="https://turim-hotels.com/en/events-en/" class="fusion-bottombar-highlight">
                                    <span class="menu-text">EVENTS</span>
                                </a>
                            </li>
                            <li id="menu-item-6095" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-6095" data-item-id="6095">
                                <a href="https://turim-hotels.com/en/corporate-en/" class="fusion-bottombar-highlight">
                                    <span class="menu-text">CORPORATE</span>
                                </a>
                            </li>
                            <li id="menu-item-19578" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-19578" data-item-id="19578">
                                <a href="https://turim-hotels.com/en/trade/" class="fusion-bottombar-highlight">
                                    <span class="menu-text">TRADE</span>
                                </a>
                            </li>
                            <li id="menu-item-6093" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-6093" data-item-id="6093">
                                <a href="https://turim-hotels.com/en/news-en/" class="fusion-bottombar-highlight">
                                    <span class="menu-text">NEWS</span>
                                </a>
                            </li>
                            <li id="menu-item-6092" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-6092" data-item-id="6092">
                                <a href="https://turim-hotels.com/en/promotions-en/" class="fusion-bottombar-highlight">
                                    <span class="menu-text">PROMOTIONS</span>
                                </a>
                            </li>
                            <li id="menu-item-6091" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-6091" data-item-id="6091">
                                <a href="https://turim-hotels.com/en/careers-en/" class="fusion-bottombar-highlight">
                                    <span class="menu-text">CAREERS</span>
                                </a>
                            </li>
                            <li id="menu-item-6096" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-6096" data-item-id="6096">
                                <a href="https://turim-hotels.com/en/contacts-en/" class="fusion-bottombar-highlight">
                                    <span class="menu-text">CONTACTS</span>
                                </a>
                            </li>
                            <li id="menu-item-3987" class="pll-parent-menu-item menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-3987 fusion-dropdown-menu" data-classes="pll-parent-menu-item" data-item-id="3987">
                                <a href="#pll_switcher" class="fusion-bottombar-highlight">
                                    <span class="menu-text">
                                        <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAMAAABBPP0LAAAAt1BMVEWSmb66z+18msdig8La3u+tYX9IaLc7W7BagbmcUW+kqMr/q6n+//+hsNv/lIr/jIGMnNLJyOP9/fyQttT/wb3/////aWn+YWF5kNT0oqz0i4ueqtIZNJjhvt/8gn//WVr/6+rN1+o9RKZwgcMPJpX/VFT9UEn+RUX8Ozv2Ly+FGzdYZrfU1e/8LS/lQkG/mbVUX60AE231hHtcdMb0mp3qYFTFwNu3w9prcqSURGNDaaIUMX5FNW5wYt7AAAAAjklEQVR4AR3HNUJEMQCGwf+L8RR36ajR+1+CEuvRdd8kK9MNAiRQNgJmVDAt1yM6kSzYVJUsPNssAk5N7ZFKjVNFAY4co6TAOI+kyQm+LFUEBEKKzuWUNB7rSH/rSnvOulOGk+QlXTBqMIrfYX4tSe2nP3iRa/KNK7uTmWJ5a9+erZ3d+18od4ytiZdvZyuKWy8o3UpTVAAAAABJRU5ErkJggg==" alt="" width="16" height="11" style="width: 16px; height: 11px;"/>
                                        <span style="margin-left:0.3em;">English</span>
                                    </span>
                                    <span class="fusion-caret">
                                        <i class="fusion-dropdown-indicator" aria-hidden="true"></i>
                                    </span>
                                </a>
                                <ul class="sub-menu">
                                    <li id="menu-item-3987-pt" class="lang-item lang-item-15 lang-item-pt lang-item-first menu-item menu-item-type-custom menu-item-object-custom menu-item-3987-pt fusion-dropdown-submenu" data-classes="lang-item">
                                        <a href="https://my-site.com/" class="fusion-bottombar-highlight" hreflang="pt-PT" lang="pt-PT">
                                            <span>
                                                <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAIAAAD5gJpuAAABL0lEQVR4AV2RA2yEQRCFNxcnRVw3qG3bthXUbZxaUaOGRVA7ro2wDWoEtW2b9/b2nHx3v943uztDSAwhAQJ86P83keCJw2HcEAJoqLivmJHbnQt+s7OFfMXHMz5DAgBPCKBCUktSRmdK43Dw3Gz451TIc68nPr+7uLzZWr2Zm78bGLxpa797e1+IC0erUeAmL+ol3R8CEE+/qqvDv2BbwjYi6yJR+6Ys5m5zA0C7HbWEIEy/KCpKC8uzQRf5fkivX3zBZIIw/Swvj8cTKthRIbDGDwcYnXRF7avy/KM5q8NZg2cDPWEaYHt8AceFACBsTdui9nmn8VWmpngawN+ngiEVHCocGM5Vpu8G0rUB5iAS0CKAYwCsg/YB1lPWKESBSCAqhMiKwHQueJwI2BeDC83C2lDIxUCuAAAAAElFTkSuQmCC" alt="" width="16" height="11" style="width: 16px; height: 11px;"/>
                                                <span style="margin-left:0.3em;">Português</span>
                                            </span>
                                        </a>
                                    </li>
                                    <li id="menu-item-3987-es" class="lang-item lang-item-445 lang-item-es menu-item menu-item-type-custom menu-item-object-custom menu-item-3987-es fusion-dropdown-submenu" data-classes="lang-item">
                                        <a href="https://my-site.com/es" class="fusion-bottombar-highlight" hreflang="es-ES" lang="es-ES">
                                            <span>
                                                <img loading="lazy" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAMAAABBPP0LAAAAflBMVEX/AAD9AAD3AADxAADrAAD/eXn9bGz8YWH8WVn6UVH5SEj5Pz/3NDT0Kir9/QD+/nL+/lT18lDt4Uf6+j/39zD39yf19R3n5wDxflXsZ1Pt4Y3x8zr0wbLs1NXz8xPj4wD37t3jmkvsUU/Bz6nrykm3vJ72IiL0FBTyDAvhAABEt4UZAAAAX0lEQVR4AQXBQUrFQBBAwXqTDkYE94Jb73+qfwVRcYxVQRBRToiUfoaVpGTrtdS9SO0Z9FR9lVy/g5c99+dKl30N5uxPuviexXEc9/msC7TOkd4kHu/Dlh4itCJ8AP4B0w4Qwmm7CFQAAAAASUVORK5CYII=" alt="" width="16" height="11" style="width: 16px; height: 11px;"/>
                                                <span style="margin-left:0.3em;">Español</span>
                                            </span>
                                        </a>
                                    </li>
                                </ul>
                            </li>
                        </ul>
                    </nav>
                    <nav class="fusion-mobile-nav-holder fusion-mobile-menu-text-align-left fusion-mobile-menu-indicator-hide" aria-label="Main Menu Mobile"></nav>
                </div>
            </div>
        </div>
        <!-- end fusion sticky header wrapper -->
    </div>
    <div class="fusion-clearfix"></div>
</header>

And my plugin so far:

<?php
add_action('rest_api_init', function () {
    register_rest_route('plugin/v1', '/json', [
        'methods' => 'GET',
        'callback' => 'page_json',
        'permission_callback' => '__return_true',
        'args' => [
            'lang' => [
                'sanitize_callback' => 'sanitize_text_field',
                'default' => 'pt',
            ],
        ],
    ]);
});

function get_lang_url($lang) {
    $urls = array(
        'pt' => 'ptUrlPlaceholder',
        'en' => 'enUrlPlaceholder',
        'es' => 'esUrlPlaceholder'
    );
    return $urls[$lang];
}

function resolve_url($base, $relative) {
    if (parse_url($relative, PHP_URL_SCHEME)) return $relative;
    $base = rtrim($base, '/');
    return $base . '/' . ltrim($relative, '/');
}

function css_fix_urls($css, $baseUrl) {
    return preg_replace_callback(
        '/url((["']?)(?!data:|https?:|//)([^"')]+)1)/i',
        function($m) use ($baseUrl) {
            $path = resolve_url($baseUrl, $m[2]);
            return "url('{$path}')";
        },
        $css
    );
}

function get_clean_dom($url) {
    $response = wp_remote_get($url);
    if (is_wp_error($response)) {
        return new WP_Error('fetch_error', 'Erro ao acessar o site externo', ['status' => 500]);
    }

    $html = wp_remote_retrieve_body($response);
    if (empty($html)) {
        return new WP_Error('empty_html', 'Conteúdo vazio recebido do site externo', ['status' => 500]);
    }

    libxml_use_internal_errors(true);
    $dom = new DOMDocument();
    $dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
    return $dom;
}

function inject_style($dom, $headerNode, $styleBlock) {
    if (!$headerNode) return;
    if ($styleBlock) {
        $frag = $dom->createDocumentFragment();
        $frag->appendXML($styleBlock);
        $headerNode->insertBefore($frag, $headerNode->firstChild);
    }
}

function remove_links_and_scripts($dom, $node) {
    $xpath = new DOMXPath($dom);
    foreach ($xpath->query('.//link[@rel="stylesheet"]', $node) as $ln) { $ln->parentNode->removeChild($ln); }
    foreach ($xpath->query('.//script', $node) as $s) {
        $s->parentNode->removeChild($s);
    }
}

function collect_and_inline_css_simple($dom, $baseUrl) {
    $xpath = new DOMXPath($dom);
    $cssParts = [];

    foreach ($xpath->query('//style') as $styleNode) {
        $cssParts[] = $styleNode->nodeValue;
    }

    foreach ($xpath->query('//link[@rel="stylesheet"]') as $linkNode) {
        $href = $linkNode->getAttribute('href');
        if (!$href) continue;
        $full = resolve_url($baseUrl, $href);
        $res = wp_remote_get($full, ['timeout'=>15]);
        if (!is_wp_error($res)) {
            $css = wp_remote_retrieve_body($res);
            $css = css_fix_urls($css, $full);
            $cssParts[] = $css;
        }
    }

    $all = implode("n", $cssParts);
    return $all ? "<style>n{$all}n</style>" : '';
}

function collect_needed_scripts($url, $xpath) {
    $scriptContent = [];

    foreach ($xpath->query('//script[@src]') as $s) {
        $src = $s->getAttribute('src');
        if (!$src) continue;
        $full = resolve_url($url, $src);

        if (strpos($full, 'jquery') !== false ||
            strpos($full, 'avada') !== false ||
            strpos($full, 'fusion') !== false) {

            $res = wp_remote_get($full, ['timeout'=>15]);
            if (!is_wp_error($res)) {
                $code = wp_remote_retrieve_body($res);
                if ($code) {
                    $scriptContent[] = "n/* Script: {$full} */n" . $code;
                }
            }
        }
    }

    foreach ($xpath->query('//script[not(@src)]') as $s) {
        $code = trim($s->nodeValue);
        if ($code) {
            $scriptContent[] = "n/* Inline Script */n" . $code;
        }
    }

    $all = implode("n", $scriptContent);
    return $all ? "<script>n{$all}n</script>" : '';
}

function page_json($request) {
    $lang = $request->get_param('lang');
    $url  = get_lang_url($lang);

    $dom = get_clean_dom($url);
    if (is_wp_error($dom)) {
        return ['error' => 'DOM error'];
    }

    $xpath = new DOMXPath($dom);
    $headerNode = $xpath->query('//header')->item(0);
    $footerNode = $xpath->query('//footer')->item(0);

    $styleBlock = collect_and_inline_css_simple($dom, $url);

    $allScripts = collect_needed_scripts($url, $xpath);

    if ($headerNode) remove_links_and_scripts($dom, $headerNode);
    if ($footerNode) remove_links_and_scripts($dom, $footerNode);

    $headerHtml = $headerNode ? $dom->saveHTML($headerNode) : '';
    $footerHtml = $footerNode ? $dom->saveHTML($footerNode) : '';

    $finalHeader = $styleBlock . "n" . $headerHtml . "n" . $allScripts;

    return [
        'header' => $finalHeader,
        'footer' => $footerHtml
    ];
}

What am I doing wrong here?

Listener for opening a section in Elementor edit panel

I want some elements in my widget to be hidden when a specific section in the Style tab is opened in Elementor.

For example, I have this section (currently empty):

$this->end_controls_section();

$this->start_controls_section('section_style_counter', [
 'label' => 'Счетчик',
 'tab'   => Controls_Manager::TAB_STYLE,
]);

$this->add_control('counter_btns', [
 'label' => 'Кнопки счетчика',
 'type'  => Controls_Manager::HEADING,
]);

How can I add a listener to detect when this section is opened?
Basically, I want to run some JS code only when this section is expanded in the Elementor panel.

I tried adding an event listener in JS on click for the section, but it seems to ignore it.

MongoDB query with Mongoose – Matching subdocument attribute within an array

I am trying to solve an issue with the format of documents in a MongoDB depicted below.

{
  "_id": {
    "$oid": "68dc4f78e11553b3647231e2"
  },
  "name": "Dutch Bros",
  "address": "25 Down Street, Salt Lake City, UT",
  "facilities": [
    "Hot drinks",
    "Food"
  ],
  "coords": [
    -0.9690884,
    51.455041
  ],
  "reviews": [
    {
      "author": "John Smith",
      "rating": {
        "$numberDecimal": "5"
      },
      "createdOn": {
        "$date": "2025-09-30T06:00:00.000Z"
      },
      "reviewText": "Great Place. Would come back again!"
    },
    {
      "author": "Jane Doe",
      "rating": {
        "$numberDecimal": "4"
      },
      "createdOn": {
        "$date": "2025-09-30T06:00:00.000Z"
      },
      "reviewText": "Okay Place. Would come back again!"
    }
  ]
}

My goal is to match review sub-document(s) based on an attribute it has such as ratings being equal to five. I know that I could take the reviews array as a whole and iterate over them to search the attributes for what I need, but I want to be able to find it directly in the MongoDB query so the MongoDB server does the work while it saves my express server any additional workload.

I guess what I am asking is how do I reference the iterable objects in the reviews array. I hope that was clear enough and I apologize if it wasn’t!

I’ve successfully implemented a version that iterates in a for loop on the express server in the controller, but this is what I want to avoid. I’ve also tried using a .get() method on the returned array because I saw that it was an available method for the object, but that yielded an error. I’ve also tried to build a query which was close to what I believe the solution is going to be but I can’t understand how MongoDB element matches an array item, see below for my failed attempt.

const monReview = await Loc
            .findById(req.params.locationid)
            .select('name reviews')
            .where('reviews')
            .elemMatch({$eq: req.params.reviewRating})
            .exec();  

Update total in Stripe payments upon shipping rate change (Amazon, Google Pay etc)

Here is some infromation about shippingratechange for Stripe express checkout.
https://docs.stripe.com/js/elements_object/express_checkout_element_shippingratechange_event

which implemented and when the Stripe payment button is clicked the modal window properly displayed:
enter image description here

Then, as you see on the image, there are several shipping options properly passed and rendered. Once another shipping option (with another price) is chosen, it triggers ‘shippingratechange’. How to update the Total because the shipping price was changed?

When I change the shipping option, it changes fine, but the Total remains unchanged, although shippingratechange gets triggered with no questions. How to set another totals value, for example 99.90 (let’s say the math calculated).

What should be added to here?

expressCheckoutElement.on('shippingratechange', function(event) {
  var resolve = event.resolve;
  var shippingRate = event.shippingRate;
  // handle shippingratechange event

  // define payload and pass it into resolve
  var payload = {
    shippingRates: [
      shippingRate
    ]
  };

  // call event.resolve within 20 seconds
  resolve(payload);
});