How to add movement restriction with transform translate?

I am making an editor on React. Canvas elements are generated using html. I added a function for moving around the canvas using transform translate. Now I can’t figure out how to add a limitation if the user has scrolled to the canvas border, now he can scroll endlessly.

The size of the screen in which the canvas is located can be different depending on the screen size. The size of the canvas itself is always 1920 by 1080 (almost always larger than the parent div).

Here is the code for moving and my attempts to add a limitation + jsx markup:

// drag
const [space, setSpace] = useState(false);
const [origin, setOrigin] = useState({ x: 0, y: 0 });
const [translate, setTranslate] = useState({ x: 0, y: 0 });

useEffect(() => {
  const scrollDown = (event: KeyboardEvent): void => {
    if (event.code === 'Space') {
      setSpace(true);
    }
  };

  const scrollUp = (event: KeyboardEvent): void => {
    if (event.code === 'Space') {
      setSpace(false);
    }
  };
  window.addEventListener('keydown', scrollDown);
  window.addEventListener('keyup',   scrollUp);
  return () => {
    window.removeEventListener('keydown', scrollDown);
    window.removeEventListener('keyup',   scrollUp);
  };
}, []);

const clampTranslate = 
  ( x: number, y: number ): 
  { x: number; y: number } | false => {
  const wrapper = scrollWrapperRef.current;
  const canvasWrapper = canvas.current;
  if (!wrapper && !canvasWrapper) return { x, y };

  const rect = wrapper.getBoundingClientRect();
  const canvasRect = canvasWrapper.getBoundingClientRect();

  const rectMaxX = rect.left + 50; // left
  const rectMinX = rect.right + 50; // right
  const rectMaxY = rect.top + 50; // top
  const rectMinY = rect.bottom + 50; // bottom

  console.log(rectMaxX, rectMinX, rectMaxY, rectMinY, x, y, canvasRect);

  if (canvasRect.right < rectMinX) {
    console.log('You have gone beyond the right edge');
  }

  if (canvasRect.left > rectMaxX) {
    console.log('You have gone beyond the left edge');
  }

  if (canvasRect.top > rectMaxY) {
    console.log('You have gone beyond the top edge.');
  }

  if (canvasRect.bottom < rectMinY) {
    console.log('You have gone beyond the bottom edge');
  }

  console.log(maxX, minX, maxY, minY);
  
  return {
    x: x,
    y: y,
  };
};

// trackpad
const handleWheel = (event: React.WheelEvent): void => {
  setIsDragging(true);

  setTranslate((prev) => {
    const next = { x: prev.x - event.deltaX, y: prev.y - event.deltaY };
    return clampTranslate(next.x, next.y);
  });
};

const handleMouseDown = (event: React.MouseEvent): void => {
  if (!space) return;
  setIsDragging(true);
  setOrigin({ x: event.clientX - translate.x, y: event.clientY - translate.y });

  event.preventDefault();
};

const handleMouseMove = (event: React.MouseEvent): void => {
  if (!space) return;
  if (!isDragging) return;
  const newX = event.clientX - origin.x;
  const newY = event.clientY - origin.y;
  
  setTranslate(clampTranslate(newX, newY));
};

const handleMouseUp = (): void => {
  setIsDragging(false);
};

<StyledEditorPageContent>
  <StyledEditorWrapper
    ref={scrollWrapperRef}
    onWheel={handleWheel}
    onMouseDown={handleMouseDown}
    onMouseMove={handleMouseMove}
    onMouseUp={handleMouseUp}
    onMouseLeave={handleMouseUp}
  >
    <StyledEditorCanvasWrapper
      key={artBoard?.id}
      id={canvasId}
      style={{
        transform: `translate(${translate.x}px, ${translate.y}px)`,
      }}
    >
      <StyledEditorCanvas
        ref={canvas}
        id={"canvas-content"}
        isHorizontal={currentOrientationCanvas === orientationsIds.horizontal}
        cursor={getCursorCanvas()}
        onClick={handleCanvasClick}
      >
        {(draft || [])?.map((item, index) => {
          return (
            <EditorLayer
              key={`${item.id}-${index}`}
              {...item}
              onClick={(): void => {
                handleLayerClick(item.id);
              }}
              index={index}
              disableDragging={isDragging}
            />
          );
        })}
      </StyledEditorCanvas>
    </StyledEditorCanvasWrapper>
  </StyledEditorWrapper>
</StyledEditorPageContent>;

In the clampTranslate function I can use the coordinates of the parent div and canvas to find out if the user has crossed the border, but I can’t figure out how to restrict the movement. If I add return false, the movement will be blocked.

NPM package – difference in running npx command vs npm command

I have bootstrapped a NPM package using npm create vite@latest and am having trouble with getting it to generate the index.d.ts file.

In my package.json I have my build script as vue-tsc -p tsconfig.build.json && vite build. Running this gives:

$ npm run build

> [email protected] build
> npx vue-tsc -p tsconfig.build.json && vite build

vite v7.0.3 building for production...
✓ 5 modules transformed.
dist/npm-package.css  0.04 kB │ gzip: 0.06 kB
dist/index.es.js      1.64 kB │ gzip: 0.81 kB │ map: 1.78 kB
dist/npm-package.css  0.04 kB │ gzip: 0.06 kB
dist/index.umd.js     1.69 kB │ gzip: 0.84 kB │ map: 1.72 kB
✓ built in 205ms

$ ls dist/
index.es.js  index.es.js.map  index.umd.js  index.umd.js.map  npm-package.css  vite.svg
$ npx vue-tsc -p tsconfig.build.json ; ls dist/
App.vue.d.ts  components/  index.d.ts  index.es.js  index.es.js.map  index.umd.js  index.umd.js.map  main.d.ts  npm-package.css  vite.svg

Placeholder inside doesnt show when i reload the page, but shows every other time

I’m new to web development and I am learning backend development of SPA using vanilla js html and css. My <textarea> in html has a placeholder that says “Let’s begin writing”. I use app.js for frontend and server.js for the main backend, and notes.js as router. When I reload the page or I run the server for the first time, when I click the create button, the textarea placeholder is empty. (Although the text placeholder is showing every time). When I click back and re-click on create button, the textarea placeholder is active. I can’t seem to figure out why this happens or how to fix it

Here is my index.html file’s textarea placeholder is

<input type="text" placeholder="What's this note about?" id="title"
            class="w-full font-bold focus:outline-none text-3xl mt-4 mb-4 text-center font-mono">

The full file:

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
    <title>Minimalist Note Taker</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link
        href="https://fonts.googleapis.com/css2?family=Inconsolata:[email protected]&family=Josefin+Sans:ital,wght@0,100..700;1,100..700&family=Martel+Sans:wght@200;300;400;600;700;800;900&display=swap"
        rel="stylesheet">
    <style>
        .font-inter {
            font-family: 'Josefin sans', sans-serif;
        }

        textarea {
            scrollbar-width: thin;
            scrollbar-color: rgba(156, 163, 175, 0.3) transparent;
        }

        textarea::-webkit-scrollbar {
            width: 6px;
        }

        textarea::-webkit-scrollbar-thumb {
            background-color: rgba(156, 163, 175, 0.3);
            /* Tailwind's gray-400 */
            border-radius: 6px;
        }

        textarea::-webkit-scrollbar-track {
            background: transparent;
        }
    </style>
</head>

<body class="bg-black hidden">
    <main id="home" class="h-screen flex flex-col justify-center items-center space-y-10">
        <h1 class="text-white text-3xl font-inter">Welcome! Ready to start noting today?</h1>
        <div class="flex items-center">
            <button id="createBtn"
                class="p-3 flex flex-col items-center justify-center w-20 rounded-md hover:scale-150 group transition-all ease-in-out duration-300">
                <img src="./assets/addnote.svg" alt="New" class="h-10">
                <span
                    class="text-[0.6rem] mt-2 text-white font-inter invisible opacity-0 group-hover:opacity-100 group-hover:visible delay-200 duration-300">NEW</span>
            </button>
            <button id="viewBtn"
                class="p-3 flex flex-col items-center justify-center w-20 rounded-md hover:scale-150 group transition-all ease-in-out duration-300">
                <img src="./assets/seenotes.svg" alt="View" class="h-10">
                <span
                    class="text-[0.6rem] mt-2 text-white font-inter invisible opacity-0 group-hover:opacity-100 group-hover:visible delay-200 duration-300">VIEW</span>
            </button>
        </div>
    </main>
    <section id="create"
        class="hidden opacity-0 scale-50 duration-150 transition-all ease-in-out text-white max-w-2xl h-[80vh] mx-auto mt-20 p-8 border border-gray-700 rounded-2xl shadow-[0_0_40px_15px_rgba(156,163,175,0.3)] flex flex-col">
        <input type="text" placeholder="What's this note about?" id="title"
            class="w-full font-bold focus:outline-none text-3xl mt-4 mb-4 text-center font-mono">
        <div class="mx-auto w-[80%] h-[1px] bg-gray-400"></div>
        <textarea name="textarea" id="textSpace" placeholder="Let's begin writing!"
            class="h-full m-8 focus:outline-none text-xl resize-none font-mono">
        </textarea>

        <div id="btnContainer" class="flex space-x-8 justify-center">
            <button id="backBtn"
                class="w-20 hover:scale-110 py-2 rounded-md text-white border-2 border-white/20 hover:border-white/60 transition ease-in-out duration-300 font-mono">BACK</button>
            <button id="saveBtn"
                class="w-20 hover:scale-110 py-2 rounded-md text-white border-2 border-white/20 hover:border-white/60 transition ease-in-out duration-300 font-mono">SAVE</button>
        </div>
    </section>

    <script src="app.js"></script>
</body>

</html>

app.js

const main = document.getElementById('home');
const create = document.getElementById('create');
// const view = document.getElementById('view');

const createBtn = document.getElementById('createBtn');
const viewBtn = document.getElementById('viewBtn');

const saveBtn = document.getElementById('saveBtn');
const backBtn = document.getElementById('backBtn');

function goHome(){
    main.classList.remove('hidden');
    create.classList.add('hidden');
}

function createPage() {
    main.classList.add('hidden');
    create.classList.remove('hidden');
    
    requestAnimationFrame(() => {
        create.classList.remove('opacity-0', 'scale-50');
        create.classList.add('opacity-100', 'scale-100');
    })
}

//route handler
function handleRoute(path){
    if(path === '/create') createPage();
    else goHome();
}

createBtn.addEventListener("click", () => {
    history.pushState({ page: 'create' }, '', '/create');
    handleRoute('/create');
})

saveBtn.addEventListener('click',async () =>  {
    const title = document.getElementById('title').value;
    const noteBody = document.getElementById('textSpace').value;
    
    const response = await fetch('/api/notes', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ title, noteBody})
    })

    const result = await response.text();
    console.log(result);
})

backBtn.addEventListener("click", () => {
    create.classList.remove('opacity-100', 'scale-100');
    create.classList.add('opacity-0', 'scale-50');

    setTimeout(() => {
        history.back();
        
        document.querySelector('#create input').value = '';
        document.querySelector('#create textarea').value = '';
    }, 150);
})

//controlling browser buttons as we add to history stack using pushstate
window.addEventListener('popstate', () => handleRoute(window.location.pathname));

//page load for manual traversal or reload
//why? app js is loaded from top down everytime when these scenarios happen
//so browser should navigate to that place before user can see the dom
handleRoute(window.location.pathname);

//show the DOM only after setting up DOM (JS reads from top to bottom, 
//sets everything above this, and then onnly unhides body)
document.body.classList.remove("hidden");

server.js

const express = require('express');
const path = require('path');
const app = express();
app.use(express.json()); //makes express talented to understand the json requests sent to it

app.use(express.static(path.join(__dirname, 'public'))); //serves all static files from the public directory

const notesRouter = require('./routes/notes');
app.use('/api/notes', notesRouter);

app.get('/{*splat}', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
})

app.listen(3000, console.log("listening"));

notes.js

const express = require('express');
const fs = require('fs');
const path = require('path');
const router = express.Router();
const notesPath = path.join(__dirname, '../data.js');

function saveNotes(newNote){
    const notes = fs.readFileSync(notesPath, 'utf-8');
    const notesArray = JSON.parse(notes);
    notesArray.push(newNote);
    fs.writeFileSync(notesPath, JSON.stringify(notesArray, '', 2));
}

router.post('/', (req, res) => {
    const id = Date.now();
    const title = req.body.title.trim();
    const noteBody = req.body.noteBody.trim();
    saveNotes({ id, title, noteBody });
    res.status(200).send('Note received');
});

module.exports = router;

The app is incomplete as i’m proceeding with the other features now
the File structure is given below

enter image description here

I tried to ask AI this issue and chatgpt said its the browser optimising itself as it considers textarea to be irrelevant during DOM loading. It then added another requestAnimationFrame inside the already active one.. yet that too didnt work.

How to load darkmode.js file dynamically?

I have a toggle button inside src/app/user-list/user-list.html, namely:

  <div class="btn-group">
    <button class="btn btn-secondary btn-sm"
            type="button"
            data-bs-theme-value="dark"
            aria-pressed="false">
      Dark
    </button>
    <button class="btn btn-light btn-sm"
            type="button"
            data-bs-theme-value="light"
            aria-pressed="false">
      Light
    </button>
  </div>

Loading it like this in src/index.html works:

<!doctype html>
<html lang="en">
  <head>...</head>
  <body>
    <app-root></app-root>
    <script src="assets/js/darkmodetoggle.js"></script>
  </body>
</html>

However, I would like to load it like this in src/app/user-list/user-list.html:

export class UserList implements OnInit {
  constructor(private renderer: Renderer2) {
  }

  ngOnInit(): void {
    this.loadScript();
  }

  loadScript() {
    if (document.getElementById('darkmode-toggle-script')) {
      console.log('Script already loaded');
      return;
    }

    const script = this.renderer.createElement('script');
    script.id = 'darkmode-toggle-script';
    script.src = 'assets/js/darkmodetoggle.js';
    script.type = 'text/javascript';
    script.onload = () => console.log('Dark Mode Script loaded dynamically');
    script.onerror = () => console.error('Error loading Dark Mode Script');

    this.renderer.appendChild(document.body, script);
  }
}

It appears inside the html in the form:

<script _ngcontent-ng-c1923262244="" id="darkmode-toggle-script" src="assets/js/darkmodetoggle.js" type="text/javascript"></script>

But the button doesn’t toggle anymore. Any idea why?

Connect to a SQLite database from a JavaScript function [closed]

I have been trying to make a connection with a database named superheroes.db in SQLite format. I need to use a custom JavaScript function to do it (I’m working on a environment named Flowise), Though for now I can’t get it to work even if it compiles.

const sqlite3 = require('sqlite3').verbose();
const path = require('path');

async function getSuperheroesSchema() {
  const dbPath = path.resolve(__dirname, 'superheroes.db');
  const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY);

  const tableName = 'superheroes';

  function getColumns() {
    return new Promise((resolve, reject) => {
      db.all(`PRAGMA table_info(${tableName});`, (err, rows) => {
        if (err) return reject(err);
        const columns = rows.map(row => {
          const notNull = row.notnull ? 'NOT NULL' : '';
          return `${row.name} ${row.type.toUpperCase()} ${notNull}`;
        });
        const columnNames = rows.map(r => r.name);
        resolve({ columns, columnNames });
      });
    });
  }

  function getSampleData(columnNames) {
    return new Promise((resolve, reject) => {
      db.all(`SELECT * FROM ${tableName} LIMIT 3;`, (err, rows) => {
        if (err) return resolve(['[ERROR FETCHING ROWS]']);
        const lines = rows.map(row =>
          columnNames.map(col => row[col]).join(' ')
        );
        resolve(lines);
      });
    });
  }

  try {
    const { columns, columnNames } = await getColumns();
    const sampleRows = await getSampleData(columnNames);

    const createStatement = `CREATE TABLE ${tableName} (${columns.join(', ')})`;
    const selectStatement = `SELECT * FROM ${tableName} LIMIT 3`;

    let output = '';
    output += `${createStatement}n`;
    output += `${selectStatement}n`;
    output += `${columnNames.join(' ')}n`;
    output += `${sampleRows.join('n')}n`;

    return output;
  } catch (err) {
    return `[ERROR] ${err.message}`;
  }
}
return await getSuperheroesSchema();


This is what I have for now. I don’t know if its me or that flowise doesn’t allow anyone to use imports for sqlite3. It should be able to establish a connection and give an agent the information about the database.

Need help connecting to a SQLite database from a JavaScript function

I have been trying for the last couple of days to make a connection with a data base named superheroes.db in SQLite format. I need to use a custom JavaScript function to do it (I’m working on a enviorment named Flowise), tho for now I cant get to make it work even if it compiles.

const sqlite3 = require('sqlite3').verbose();
const path = require('path');

async function getSuperheroesSchema() {
  const dbPath = path.resolve(__dirname, 'superheroes.db');
  const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY);

  const tableName = 'superheroes';

  function getColumns() {
    return new Promise((resolve, reject) => {
      db.all(`PRAGMA table_info(${tableName});`, (err, rows) => {
        if (err) return reject(err);
        const columns = rows.map(row => {
          const notNull = row.notnull ? 'NOT NULL' : '';
          return `${row.name} ${row.type.toUpperCase()} ${notNull}`;
        });
        const columnNames = rows.map(r => r.name);
        resolve({ columns, columnNames });
      });
    });
  }

  function getSampleData(columnNames) {
    return new Promise((resolve, reject) => {
      db.all(`SELECT * FROM ${tableName} LIMIT 3;`, (err, rows) => {
        if (err) return resolve(['[ERROR FETCHING ROWS]']);
        const lines = rows.map(row =>
          columnNames.map(col => row[col]).join(' ')
        );
        resolve(lines);
      });
    });
  }

  try {
    const { columns, columnNames } = await getColumns();
    const sampleRows = await getSampleData(columnNames);

    const createStatement = `CREATE TABLE ${tableName} (${columns.join(', ')})`;
    const selectStatement = `SELECT * FROM ${tableName} LIMIT 3`;

    let output = '';
    output += `${createStatement}n`;
    output += `${selectStatement}n`;
    output += `${columnNames.join(' ')}n`;
    output += `${sampleRows.join('n')}n`;

    return output;
  } catch (err) {
    return `[ERROR] ${err.message}`;
  }
}
return await getSuperheroesSchema();


This is what I have for now. I dont know if its me or that flowise dont allow anyone to use imports for sqlite3. It should be able to establish a connection and give an agent the information about the database.

Why does submitting a form in React 19 reset checkbox inputs but not text inputs?

It appears that when a callback is supplied to a form’s action prop and the form is submitted, checkbox inputs get reset while text inputs do not.

Consider the following example:

import React, { useState } from 'react';
import { createRoot } from 'react-dom/client'

function App() {
  const [value, setValue] = useState('')
  const [checked, setChecked] = useState(false)
  
  return <form action={() => {}}>
    <input
      type="text"
      value={value}
      onChange={({ target }) => setValue(target.value)}
    />
    <input
      type="checkbox"
      checked={checked}
      onChange={({ target }) => setChecked(target.checked)}
    />
    <button>Submit</button>
  </form>
}

createRoot(document.getElementById('root')).render(<App />)

You can see a live example here.

To replicate the issue, perform the following steps:

  1. Add some text to the text input
  2. Check the checkbox
  3. Click “Submit”

Once you submit the form, the text input will have retained its value while the checkbox input will have been unchecked.

What’s going on here?

How to fix “Search functionality must be placed within a landmark region” WCAG issue with input and icon?

I’m trying to fix a WCAG accessibility issue flagged by tools like axe/lighthouse:

Search functionality must be placed within a landmark region
input.v-input

  • The input is wrapped in a div with role=”search” — so I assume it’s already in a landmark.
  • There’s also an icon next to the input, and I suspected it might be causing the problem.
  • I tried setting both role=”presentation” and aria-hidden=”true” on the and its parent elements — but the issue still persists.

Is there a more semantic or reliable way to mark the search input so that it passes WCAG audits?

Here is a simplified version of my HTML:

<div role="search" class="v-input-with-icon-container trade-in-criteria__filter-input">
  <input
    type="search"
    aria-label="Type here to search for device"
    placeholder="Type here to search for device"
    class="v-input"
  >
  <span class="content v-input__icon" role="none" aria-hidden="true">
    <span>
      <img
        src="https://via.placeholder.com/16"
        alt=""
        role="presentation"
        aria-hidden="true"
      >
    </span>
  </span>
</div>

How can I hide all other images in a gallery without listing them individually?

I’m trying to make an image gallery for a personal website that will create a pop-up window from a thumbnail to a bigger image, and it more or less works, but the only way I’ve found that works requires that I individually list and set the display to none for every image I don’t want visible, and set the display to block for the one that I do.

This is what I’m trying:

function jvjponder(imgs) {
  var expandImg = document.getElementById("jvjponder");
  var imgText = document.getElementById("imgtext");
  expandImg.src.thmb = imgs.src;
  imgText.innerHTML = imgs.alt;
  expandImg.parentElement.style.display = "block";
  // the bit that isn't working 
  if (!document.getElementById('jvjponder')) {
    set.style.display = 'none';
  }
}

This is what works (except there are significantly more lines like this):

document.getElementById('jvjponder').style.display = 'block';
document.getElementById('jhangman').style.display = 'none';

(which takes the place of the ‘if’ statement)

I’m not very familiar with js, so most of this is copied and pasted from w3, except for the last bit. I’m trying to find a simpler/more condensed way to say “if it isn’t this image, set display to none” but what I have isn’t working, and instead all of the images are displayed at once. Can someone who understands javascript please tell me what I should do?

how can i get original file path in web enviroment? [closed]

I have a file upload feature in my web program, and after uploading, I want to delete the original file. However, due to browser restrictions and security reasons, JavaScript cannot send the original file path to the backend. Is there a way to do this? Preferably without using external programs.

I don’t want to separately ask users for the path or anything like that, I want it to work dynamically.

  • In addition, the server and the client at different enviroment.

Why did I think so, Our cell phones ask for the request for permissions and get permission from the app. Of course, it’s different from the web.

I’m just studying, so it’s hard, so please help me a little bit

Automatic printing with js and php [closed]

I want a complete js php code that generates a PDF report (for example an invoice) and then prints it automatically without displaying a print preview.
Help me find a solution

I tried this

 window.print()

but it only displays the web page (web page preview)

My fpdf2 PDF is blank when I try to download it from the browser after passing it to the front end

I cannot for the life of me figure out why this PDF won’t display. I’m using fpdf2 to create a pdf that looks good when saving it from the python script. However, something must be going wrong when passing the data to the front end.

Here is the pdf representation that I’m ending up with:

"%PDF-1.3
1 0 obj
<<
/Count 1
/Kids [3 0 R]
/MediaBox [0 0 595.28 841.89]
/Type /Pages
>>
endobj
2 0 obj
<<
/OpenAction [3 0 R /FitH null]
/PageLayout /OneColumn
/Pages 1 0 R
/Type /Catalog
>>
endobj
3 0 obj
<<
/Contents 4 0 R
/Parent 1 0 R
/Resources 6 0 R
/Type /Page
>>
endobj
4 0 obj
<<
/Filter /FlateDecode
/Length 72
>>
stream
x�3R��2�35W(�r
Q�w3T04�30PISp
    �Z(�[����(hx����+���(j*�d��V
endstream
endobj
5 0 obj
<<
/BaseFont /Helvetica-Bold
/Encoding /WinAnsiEncoding
/Subtype /Type1
/Type /Font
>>
endobj
6 0 obj
<<
/Font <</F1 5 0 R>>
/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
>>
endobj
7 0 obj
<<
/CreationDate (D:20250708064952Z)
>>
endobj
xref
0 8
0000000000 65535 f 
0000000009 00000 n 
0000000096 00000 n 
0000000199 00000 n 
0000000279 00000 n 
0000000422 00000 n 
0000000524 00000 n 
0000000611 00000 n 
trailer
<<
/Size 8
/Root 2 0 R
/Info 7 0 R
/ID [<119889D12E2230568C66D2CFBCFCE0FB><119889D12E2230568C66D2CFBCFCE0FB>]
>>
startxref
666
%%EOF
"

I can verify that this is the data getting downloaded when I change the type to text/plain. When the type is set to application/pdf, I end up with a blank pdf. I can also tell that part of it is working properly because adding more pages to the pdf results in a download with the proper amount of pages.

Running out of ideas, so I’d really appreciate if anyone had any insight.
Thanks in advance!

I’ve tried converting to different types, using a Blob() vs a File(), changing headers to include “Content-Disposition”: “attachment; filename=myfilename.pdf” and more.

Some of these ideas came from previous posts, but none of them quite did the trick.

ReactJS – Javascript addEventListener does not remove the listener function at component’s unMount

I have added an event listener to “keydown” event in my ReactJS application. Because of a third party library, I’m adding it via setTimeout in a useEffect, this component is like a popup, so it mounts-unmounts lots of times in the app lifecycle. Its like below;

      useEffect(() => {
        const timeoutID = setTimeout(() => {
          clearTimeout(timeoutID);
            window.addEventListener("keydown", theFunction, true);
          
        }, 500);
        
        function theFunction(event: KeyboardEvent) {
           // do something
        }

        return () => {
          window.removeEventListener("keydown", theFunction, true);
        };
      }, []);

It’s a typical usage, to remove event listener using removeEventListener method. The problem is, when the component unMounts, the event stays and stacks up on top of another. I’m actually seeing it on the Event Listeners tab on Devtools.

I was suspecting that, every time this component mounts, theFunction changes in the memory, so it’s not the same function and therefore React can’t remove it. I took theFunction from the useEffect and put it to the top of the component (outside of the functional component definition, just below the imports).

// imports

function theFunction(event: KeyboardEvent) {
               // do something
            }

const Component = () => {
          useEffect(() => {
            const timeoutID = setTimeout(() => {
              clearTimeout(timeoutID);
                window.addEventListener("keydown", theFunction, true);
              
            }, 500);
            
            return () => {
              window.removeEventListener("keydown", theFunction, true);
            };
          }, []);

And voila! The event is now adding just once and does not stack.

I’m not sure is it really beacuse theFunction was not same each time (as I thought) the component unMounts, or is there another React thing that I’m missing. Why React does not removes this eventListener on unMount in this case?

Browser extension fails to reliably update favicon on SPA pages despite correct in DOM

I’m developing a browser extension that changes the favicon on a Single Page Application (SPA).. specifically Roblox.com. The extension injects code to replace the existing favicon with a custom SVG base64 data URI.

When the page initially loads, the extension successfully changes the favicon, and the DOM shows the new <link rel="icon"> tag. However, when navigating to other SPA routes (e.g., /trades), although the DOM still has the updated favicon tag inserted by my extension, the browser sometimes reverts to the default Roblox favicon.

I’ve confirmed via DevTools that:

  • The <link rel="icon"> element’s href attribute is correctly set to my custom SVG data URI on all routes.
  • There are no conflicting multiple favicon tags in the DOM.
  • The extension’s console logs indicate the favicon set function runs on route changes.

What I’ve tried:

  • Removing and re-adding the <link rel="icon"> element programmatically on every SPA route change.
  • Adding a random query parameter to the favicon href as a cache buster.
  • Monitoring DOM mutations to reactively reset the favicon.
  • Tested on FireFox

Question:

How can a browser extension reliably update the favicon on SPA sites where the page does not do full reloads, and the favicon sometimes reverts despite the DOM containing the correct <link rel="icon">? Are there known tricks or workarounds for favicon caching or SPA dynamic page handling specifically for extensions?

Current Code:

const f = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IlIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDcyIDcyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDojRTIyMzFBO30NCgkuc3Qxe2ZpbGw6I0ZGRkZGRjt9DQo8L3N0eWxlPg0KPGcgaWQ9IlJfMV8iPg0KCTxnPg0KCQk8cGF0aCBjbGFzcz0ic3QwIiBkPSJNNjkuMyw2Ny4zQzU5LDU3LjgsNTAuMSw1MC4xLDQwLjcsNDIuMmM1LjYtMC4yLDExLjctMC41LDE3LjMtMWMwLjYtMC4xLDEuMi0wLjMsMS43LTAuNw0KCQkJYzIuMS0xLjcsNS4xLTQuNyw3LTcuM2MwLjMtMC41LDAuNS0xLDAuMy0xLjZjLTItOS01LjQtMTkuOC0xMC4zLTI5LjRjLTAuNC0wLjctMS4yLTEuMi0yLTEuMkMzNi42LDIuMSwxOS4zLDQuNiw0LDcuNw0KCQkJQzMsNy45LDIuMyw4LjgsMi4zLDkuOEMyLjYsMjguNCwzLjYsNDcuNiw1LDY2LjVjMC4xLDEsMC44LDEuOCwxLjgsMmM2LjEsMC45LDEyLjEsMS43LDIwLjMsMi4yYzAuNywwLDEuMy0wLjcsMS0xLjQNCgkJCWMtMi44LTcuOS03LjItMjMuNi03LjktMjYuNGMwLjcsMCwxLTAuMSwxLjYtMC4xYzMuNyw3LjUsOS4zLDE3LjksMTMuMywyNi41YzAuNSwxLjEsMS42LDEuOCwyLjgsMS44YzkuNSwwLjIsMTkuOS0wLjIsMzAuOC0xLjgNCgkJCUM2OS42LDY5LDcwLDY3LjksNjkuMyw2Ny4zeiBNMzQuMSwyNi44Yy0zLjYsMC40LTkuMSwwLjgtMTIuNiwxYy0wLjMtMi4yLTAuNi01LjQtMC43LTcuN2M0LjMsMC4xLDguNywwLjIsMTMuMywwLjYNCgkJCUMzNC4xLDIyLjcsMzQuMSwyNC43LDM0LjEsMjYuOHoiLz4NCgk8L2c+DQoJPGc+DQoJCTxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik01Ny42LDM3LjVjMS44LTEuNiwzLjktMy42LDUuMy01LjlDNjEsMjMuMiw1OCwxNCw1My45LDUuNEMzOC4yLDYuNSwyMS4yLDguNyw2LjQsMTEuNWMwLjYsMTkuOCwxLjUsMzYsMyw1Mw0KCQkJYzQuNywwLjcsNy45LDEuMSwxMy4yLDEuNUMyMCw1Ni44LDE4LDQ5LjMsMTYuMSw0MGMyLjUtMC4xLDUuMS0wLjIsNy43LTAuM2M2LjIsMTAsOS44LDE3LjIsMTUuOCwyN2M3LjQsMCwxNC4yLTAuMiwyMS4xLTAuOA0KCQkJYy05LjktOS43LTE5LjEtMTguMy0yOC41LTI2LjZDNDEuMiwzOC44LDUwLjMsMzguMiw1Ny42LDM3LjV6IE0xNy45LDMxLjZjLTAuNy01LTAuOS0xMC0xLjEtMTVjNywwLDE0LjQsMC4zLDIxLjMsMC45DQoJCQljMC4yLDMuNCwwLjIsOSwwLDEyLjVDMzEuNiwzMC43LDI0LjgsMzEuMiwxNy45LDMxLjZ6Ii8+DQoJPC9nPg0KPC9nPg0KPC9zdmc+DQo=";
function setF() {
  let link = document.querySelector('link[rel="icon"], link[rel="shortcut icon"]');
  if (!link) {
    link = document.createElement('link');
    link.rel = 'icon';
    document.head.appendChild(link);
  }
  if (link.href !== f) {
    link.href = f;
    link.type = 'image/svg+xml';
    console.log('[POM] favicon set');
  }
}
function watchFaviconAndURL() {
  setF();
  const observer = new MutationObserver(mutations => {
    for (const m of mutations) {
      if (
        m.type === 'childList' &&
        [...m.removedNodes].some(n =>
          n.tagName === 'LINK' && (n.rel === 'icon' || n.rel === 'shortcut icon')
        )
      ) {
        setF();
      }
      if (
        m.type === 'attributes' &&
        m.target.tagName === 'LINK' &&
        (m.target.rel === 'icon' || m.target.rel === 'shortcut icon') &&
        m.attributeName === 'href' &&
        m.target.href !== f
      ) {
        setF();
      }
    }
  });
  observer.observe(document.head, {
    childList: true,
    subtree: true,
    attributes: true,
    attributeFilter: ['href', 'rel', 'href'],
  });
  let lastURL = location.href;
  ['pushState', 'replaceState'].forEach(fnName => {
    const original = history[fnName];
    history[fnName] = function () {
      const result = original.apply(this, arguments);
      checkURLChange();
      return result;
    };
  });
  window.addEventListener('popstate', checkURLChange);
  function checkURLChange() {
    if (location.href !== lastURL) {
      lastURL = location.href;
      setF();
    }
  }
  setInterval(() => {
    if (location.href !== lastURL) {
      lastURL = location.href;
      setF();
    }
  }, 500);
  setInterval(setF, 2000);
}

watchFaviconAndURL();