Overlapping lines weeks

I have datasets with date time as x-axis.
I did split the datas by weeks like “2024W41”, “2024w42″…

I would like to display two lines, one for each week.
Sadly, as its based on date for x-axis, the first week appears left, the second one appears next, to the right of it. Its logical.

How could i make those weeks overlap ? (like they all start from he begining of the x-axis).
Because i want to display as X-axis days of weeks (monday…sunday).

Here is what i have now :
Here is what i have now
Here is what i would like to do :
Here is what i would like to do

Please help me text file edit in powershell related problem? [closed]

I have a text file which has the following input

ST Loan 2023 RABI DMR Report KOTA DCCB, RAMGANJ MANDI BRANCH BRANCH, KHEEMACH PACS 

Application No. BL00181453 Bhamashah No. 9999-YA5Z-00007 DMR A/c No. DMR27000000925542

Customer Name SUNITA Father's Name GAJANAND Caste GEN

Farmer Type LARGE Crop RABI Share Capital Rs. 9,600.00

Agri Insurance Rs. 3,084.83 Life Insurance Rs. 0.00 Accidental Insurance Rs. 0.00

MCL Amt. Rs. 119,225.00 Sanctioned Amt. Rs. 116,229.00

TRANSACTION DETAILS

Transaction Date Description Debit Credit Outstanding Balance (Dr) Remaining Limit

08-02-2024 DR PACS MAIN TO CR SB : 2700711154005777 - TRANSFER WITHDRAW 92,430.00 0.00 92,430.00 23,799.00

28-03-2024 AGRI INSURANCE DR FROM PACSMAIN:27007610640004900 - - 1,296.83 0.00 93,726.83 22,502.17

13-06-2024 CR FROM PACS MAIN:2700761064000490 - MATM AGT CR OR NO BOOK DEPOSIT TRANSFER 0.00 93,726.83 0.00 116,229.00

13-06-2024 DR 3% INT FROM DMR ACCT : DMR27000000925542 - - 962.79 0.00 962.79 115,266.21

13-06-2024 CR 3% INT IN DMR ACCT : DMR27000000925542 - - 0.00 962.79 0.00 116,229.00

13-06-2024 DR 4% INT FROM DMR ACCT : DMR27000000925542 - - 1,283.72 0.00 1,283.72 114,945.28

13-06-2024 CR 4% INT IN DMR ACCT : DMR27000000925542 - - 0.00 1,283.72 0.00 116,229.00 

and output something

ST Loan 2023 RABI DMR Report KOTA DCCB, RAMGANJ MANDI BRANCH BRANCH, KHEEMACH PACS | None | None | None | None

09-01-2024 DR PACS MAIN TO CR SB : 2700510574004549 - TRANSFER WITHDRAW 29,809.00 0.00 29,809.00 0.00 | SUNITA | GAJANAND | BL00181453  | DMR27000000925542

31-08-2024 CR FROM PACS MAIN:2700561064000199 - MATM AGT CR OR NO BOOK DEPOSIT TRANSFER 0.00 29,809.00 0.00 29,809.00 | SUNITA | GAJANAND | BL00181453  | DMR27000000925542

31-08-2024 DR 3% INT FROM DMR ACCT : DMR27000000912329 - - 574.19 0.00 574.19 29,234.81 | SUNITA | GAJANAND | BL00181453  | DMR27000000925542

31-08-2024 CR 3% INT IN DMR ACCT : DMR27000000912329 - - 0.00 574.19 0.00 29,809.00 | SUNITA | GAJANAND | BL00181453  | DMR27000000925542

31-08-2024 DR 4% INT FROM- DMR ACCT : DMR27000000912329 - - 765.59 0.00 765.59 29,043.42 | SUNITA | GAJANAND | BL00181453  | DMR27000000925542

31-08-2024 CR 4% INT IN DMR ACCT : DMR27000000912329 - - 0.00 765.59 0.00 29,809.00 | SUNITA | GAJANAND | BL00181453  | DMR27000000925542

Can anyone help me to get the output like this ?

Can someone write me the powershell code ?

Please help me this problem

Easier way to define JSDoc type hints?

I’d like to use JSDoc type hints as much as possible.
But in some cases, defining hints is tricky.

In this case, it is Svelte‘s Store.

After much effort, I succeeded with the following code:

import { writable } from 'svelte/store';

/** @type{import('svelte/store').Writable<{nick:string, img:string}|null>} */
export const userInfoStore = writable(null);

The above code works perfectly in my VS Code environment.
But it is a bit annoying.

So I also found the following way, with a very, very small overhead:

export const userInfoStore2 = writable((
  (
    /** @type{{nick:string, img:string}|null} */
    val
  ) => val
)(null));

This works perfectly, but I don’t like the fact that the actual code is affected, not the comments, just for my convenience.
This is my best.

I tried several codes like the following, but they all failed:

export const userInfoStore3 = writable(
  /** @type{{nick:string, img:string}|null} */
  null
);
/** @type{{nick:string, img:string}|null} */
let userInfoDef4 = null;
export const userInfoStore4 = writable(userInfoDef4);
let userInfoDef5 = null;
export const userInfoStore5 = writable(
  /** @type{{nick:string, img:string}|null} */
  userInfoDef5
);

Any other ideas?

Customizing Input Type Date Popup to Match Dark Theme

I’m currently developing a dark-themed website and have run into an issue with the <input type="date"> element. When I click on the date input, the date picker popup appears in a light theme, which disrupts the overall aesthetic of my site.

I’ve tried several approaches, including CSS overrides. However, I still haven’t found a satisfactory solution that allows me to customize the date picker to fit my dark theme fully. Image of the problem

I’m looking for suggestions on how to style the date picker with better customization options to match a dark design. Any insights or advice would be greatly appreciated!

Thanks in advance for your help!

Why my recursive fractal don’t draw two branches of the tree

I just found a simple fractal that I want to recreate with P5.js

Simple fractal

This is my code:

function setup() {
    createCanvas(400, 400);
    noLoop();
    angleMode(DEGREES)
}

function draw() {
    translate(width / 2, height / 2);
    fill(0,0,0);
    fractal(-10, -100, 10, 200, 0.7);
}


function fractal(x, y, w, h, factor) {
    if (w > 1 && h > 1) {
        rect(x, y, w, h);
        push();
        rotate(90);
        translate(y, 0);
        scale(factor);
        fractal(x, y, w, h, factor);
        pop();

        rect(x, y, w, h);
        push();
        rotate(90);
        translate(-y, 0);
        scale(factor);
        fractal(x, y, w, h, factor);
        pop();
    }
}
html, body {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}
html {
    flex-direction: column;
}
body {
    flex-direction: row;
    margin: 0;
}
canvas {
    margin: auto;
    display: block;
    box-shadow: 0px 2px 12px -2px rgb(0 0 0 / 30%);
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.min.js"></script>

I probably do something silly, but I’m not able to find where I made a mistake.

I only draw one branch of the fractal. If I comment out code between push() and pop() it draws the other branch.

Textarea doesn’t display the encoded output correctly

I get user input from textarea and handle it on the server where it’s encoded with base64 and stored in a database, but for some reason there seems to be an encoding error when loading that content back into a textarea.

The problem is that i my case quotation marks ' and " get encoded by the form but when i display my text again the encoding remains. (it looks like sometext&#39;moretext instead of sometext'moretext) I wonder how can i decode the values to display correctly again.

I tried decodeURIcomponent() but it didn’t work. I use razor pages and load data into textarea using asp-for="" directive.

[slug]/page.tsx re-renders static elements

i am creating a docs page in next.js using .mdx files.
in my docs pages it has two elemnts:

<div className={'flex flex-row'}>
            <DocsNav/>
            <div className={'flex flex-col'}>
                <EditPage docName={params.slug.toString()}/>
                <div className={'my-4'}>{content}</div>
            </div>

        </div>

now what’s happening is when i click on a new docs link, it shifts me from one docs page to another, but the problem is the : <DocsNav/> is getting re-created everytime i click on an item.

This is my project structure :

so suppose when i go from
/docs/home to docs/strings the navBar is getting recreate again and again as a results the NavBar gets shrinked again.

Its an open source project so you can check the GitHub repository directly :
Prismio Website

I tired few approach given by ChatGPT but non seems to work.

Please take a look on my please.
Thank you.

NW.js problem is happening in Manjaro Linux, and still this problem was not solved

I installed NW.js with linux command like below:

npm install node-webkit-builder -g

After I install, I tried to make *.exe file with this NW builder.
But, still can’t understand the problem is happening
when I try to below commands:

nwbuild --platform "win32, win64
nwbuild

Below is the error code when I trying to enter that command:

TypeError: Cannot read properties of undefined (reading 'replace')
at Object.parse (file:///usr/lib/node_modules/nw-builder/src/util.js:206:39)
at nwbuild (file:///usr/lib/node_modules/nw-builder/src/index.js:46:26)
at file:///usr/lib/node_modules/nw-builder/src/cli.js:28:1
at ModuleJob.run (node:internal/modules/esm/module_job:262:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:482:26)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5)
file:///usr/lib/node_modules/nw-builder/src/util.js:206
options.app.name = options.app.name.replace(/[<>:"/\|?*u0000-u001F]/g, '');
TypeError: Cannot read properties of undefined (reading 'replace')
at Object.parse (file:///usr/lib/node_modules/nw-builder/src/util.js:206:39)
at nwbuild (file:///usr/lib/node_modules/nw-builder/src/index.js:46:26)
at file:///usr/lib/node_modules/nw-builder/src/cli.js:28:1
at ModuleJob.run (node:internal/modules/esm/module_job:262:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:482:26)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5)
Node.js v22.8.0

Can someone tell me, please why is this error happening in my coding?

I already asked a lot of time for chatGPT with that error code, and I already asked the Manjaro community.

https://forum.manjaro.org/t/theres-a-problem-in-nw-js/169599

But they didn’t find this problem solution.

The saying Check the script but I’m not sure about that.

Remix, Supabase Oauth: OAuth error: AuthApiError: invalid request: both auth code and code verifier should be non-empty

I am trying to implement oAuth (Github in this case) in my Remix.run app using Supabase.

This is my signup action:

export const signupWithOAuth = async ({
  request,
  provider,
}: {
  provider: 'google' | 'github'
  request: Request
}) => {
  const { supabaseClient } = createSupabaseServerClient({ request })

  const { data, error } = await supabaseClient.auth.signInWithOAuth({
    provider: provider,
    options: {
      redirectTo: `${new URL(request.url).origin}/auth/callback`,
    },
  })


  if (error) {
    console.error(error)
    throw new Error(`Failed to sign up with OAuth, ${error.message}`)
  }


  return { data }
}

This is my auth.callback.ts

import { redirect, type LoaderFunctionArgs } from '@remix-run/node'
import {
  createServerClient,
  parse,
  parseCookieHeader,
  serializeCookieHeader,
} from '@supabase/ssr'

export async function loader({ request }: LoaderFunctionArgs) {
  const requestUrl = new URL(request.url)
  const code = requestUrl.searchParams.get('code')
  const next = requestUrl.searchParams.get('next') || '/'
  const headers = new Headers()

  console.log('Request URL:', requestUrl)
  console.log('Code:', code)
  console.log('Next:', next)

  if (code) {
    const cookies = parse(request.headers.get('Cookie') ?? '')
    const supabase = createServerClient(
      process.env.SUPABASE_URL!,
      process.env.SUPABASE_ANON_KEY!,
      {
        cookies: {
          getAll() {
            return parseCookieHeader(request.headers.get('Cookie') ?? '')
          },
          setAll(cookiesToSet) {
            cookiesToSet.forEach(({ name, value, options }) =>
              headers.append(
                'Set-Cookie',
                serializeCookieHeader(name, value, options)
              )
            )
          },
        },
      }
    )

    const { error } = await supabase.auth.exchangeCodeForSession(code)

    if (error) {
      console.error('OAuth error:', error)
    }

    if (!error) {
      return redirect(next, { headers })
    }
  }

  return redirect('/auth/auth-code-error', { headers })
}

(I copied it from: https://supabase.com/docs/guides/auth/social-login/auth-github?queryGroups=environment&environment=server&queryGroups=framework&framework=remix. Some of the things are deprecated but I am ignoring that for now).

I am fairly sure that I’ve setup Github and Supabase correctly.

This is the error that I get logged:
OAuth error: AuthApiError: invalid request: both auth code and code verifier should be non-empty

I am really at a loss what to do here. Maybe someone has an idea?

Thanks in advance.

How can i calculate the average of a custom event value on GA4?

i have login time for multiple users and i want to show the average, min and max of the login time. how can i do that on GA4 ? following is my code:

gtag('event', 'login_time', {
    event_category: 'timing',
    event_label: 'Time to login',
    value: Math.round(timeToLogin) // Time in milliseconds
  })

i am able to get the login time on GA4 but it shows me the sum of all values. how can i get the average instead

enter image description here

Looking for a data table management script/system

I am looking for a data table management script that maybe uses something like datatables.net structure in combination with a nice admin area where you can add columns, data entries, pages and so on to your mysql tables.
Anything out there? It doesn’t have to be free.

Thanks in advance.
Best greetings

Able to read from google sheet but unable to write to google sheet using Javascript

I’m able to read rows from my google sheet, but unable to write to it (append rows)..

That probably means that I’m clear on the whole authentication business right?

Below is my code for trying to write to the first two cells of my sheet. I don’t get any error message, I get a message that data has been successfully sent to the sheet, but I don’t see anything updated on my sheet

    const url = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/${sheetName}!A1:B1:append?valueInputOption=USER_ENTERED`; 
    chrome.identity.getAuthToken({ interactive: true }, function (token) {
      console.log(token);

      fetch(url, {
        method: "POST",
        range: "Sheet1!A1:B1",
        majorDimension: "ROWS",
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          values: [[1, 2]],
        }),
      })
        .then((response) => response.json())
        .then((data) => sendResponse({ data }))
        .catch((error) => sendResponse({ error: error.message }));
    });

Below is my code for reading from the sheet. This works!

    const url = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/${sheetName}?key=${apiKey}`;
    chrome.identity.getAuthToken({ interactive: true }, function (token) {
      console.log(token);

     fetch(url)
        .then((response) => response.json())
        .then((data) => sendResponse({ data }))
        .catch((error) => sendResponse({ error: error.message }));
    });

In my manifest.json, I have as follows

 "oauth2": {
    "client_id": "<CLIENT ID>.apps.googleusercontent.com",
    "scopes": ["https://www.googleapis.com/auth/spreadsheets"]
  }

I get past the authorization/consent screen and any credential confirming steps, but I just don’t see anything updated on the actual sheet when I’m trying to write!

I tried changing the access level of the google sheet itself from Restricted to Anyone with link. For reading, setting the level to Anyone with link worked. For writing, neither of them worked.

I enabled viewing of protected range in the google sheet. Still nothing was written.

how to adjust draggableElement to have correct x and y position

I have implemented chart js to plot a two curve indicated by red and blue line. I want to add a slider (black vertical line) so that it remains always within two curves and user can move it left and right of the plot. when users slides it left or right, it would adjust the height itself. So taking the x and y value of lower plot and setting height by subtracting y1 of first plot and y2 of second plot, it behaves as expected. Here is my code,

    // Initialize the draggable line
    $(function() {
      $("#draggable").draggable({
        axis: "x",
        containment: "#myChart",
        drag: function(event, ui) {
          const canvas = document.getElementById('myChart');
          const ctx = canvas.getContext('2d');
          const rect = canvas.getBoundingClientRect();
          const chartLeft = rect.left;
          
          const xPos = ui.position.left; // Position of the draggable line
          const xValue = myChart.scales.x.getValueForPixel(xPos); // X value on the chart

          // Find the nearest points on the datasets
          const pYValue = getYValueAtX(xValue, myChart.data.datasets[0].data);
          const sYValue = getYValueAtX(xValue, myChart.data.datasets[1].data);

          const difference = pYValue && sYValue ? (pYValue - sYValue) : null;

          // Update the tooltip with the current x, p, and s values
          const tooltip = document.getElementById('tooltip');
          tooltip.innerHTML = `X: ${xValue.toFixed(2)}<br>P: ${pYValue ? pYValue.toFixed(2) : 'N/A'}<br>S: ${sYValue ? sYValue.toFixed(2) : 'N/A'}<br>Difference: ${difference ? difference.toFixed(2) : 'N/A'}`;
          tooltip.style.display = 'block';
          tooltip.style.left = `${xPos + chartLeft + 10}px`;
          tooltip.style.top = `${rect.top + 10}px`;


    const xPixelPos = myChart.scales.x.getPixelForValue(xValue); // Get pixel for xValue
    const yPixelPos = myChart.scales.y.getPixelForValue(pYValue); // Get pixel for sYValue

    const y1PixelPos = myChart.scales.y.getPixelForValue(sYValue); // Get pixel for sYValue


    const height = Math.abs(yPixelPos - y1PixelPos);

    const blackLine = document.getElementById('draggable');
    blackLine.style.left = `${xPixelPos}px`; // Set the x position of the div
    blackLine.style.top = `${yPixelPos}px`;
    blackLine.style.height = `${height}px`; 

    console.log("xpixel:", xPixelPos, "ypixel:", yPixelPos, "y1pixel:", y1PixelPos, "height:", height);


draggableElement.style.height = `${newHeight}px`; // Set height



        }
      });
    });

    // Helper function to find Y value for a given X in the dataset
    function getYValueAtX(x, data) {
      // Find the nearest point in the data for the given x
      const point = data.find(p => p.x >= x);
      return point ? point.y : null;
    }


function interpolateData(data) {
  // Create arrays to store the new interpolated p and s values
  let interpolatedData = [];
  
  for (let i = 0; i < data.length; i++) {
    const currentPoint = data[i];
    const nextPoint = data[i + 1];

    // Check if "p" or "s" is missing and interpolate if necessary
    if (currentPoint.p === "" && nextPoint) {
      // Linear interpolation for 'p'
      const prevPoint = data[i - 1];
      if (prevPoint && nextPoint.p !== "") {
        currentPoint.p = prevPoint.p + ((nextPoint.x - prevPoint.x) * (nextPoint.p - prevPoint.p)) / (nextPoint.x - prevPoint.x);
      }
    }

    if (currentPoint.s === "" && nextPoint) {
      // Linear interpolation for 's'
      const prevPoint = data[i - 1];
      if (prevPoint && nextPoint.s !== "") {
        currentPoint.s = prevPoint.s + ((nextPoint.x - prevPoint.x) * (nextPoint.s - prevPoint.s)) / (nextPoint.x - prevPoint.x);
      }
    }

    // Push the currentPoint to the interpolatedData
    interpolatedData.push(currentPoint);
  }

  return interpolatedData;
}


    // AJAX function to fetch JSON data
    function fetchJSONFile(filePath, callback) {
      var xhr = new XMLHttpRequest();
      xhr.open('GET', filePath, true);
      xhr.responseType = 'json';
      xhr.onload = function() {
        if (xhr.status === 200) {
      const interpolatedData = interpolateData(xhr.response);
      callback(interpolatedData);
        } else {
          console.error('Failed to load JSON file.');
        }
      };
      xhr.send();
    }

    // Callback to process the data and plot the chart
    function plotChart(jsonData) {
      const pData = jsonData
        .filter(item => item.p !== "")
        .map(item => ({
          x: item.x,
          y: item.p
        }));
      const sData = jsonData
        .filter(item => item.s !== "")
        .map(item => ({
          x: item.x,
          y: item.s
        }));

      // Chart.js configuration
      const ctx = document.getElementById('myChart').getContext('2d');
      myChart = new Chart(ctx, {
        type: 'line',
        data: {
          datasets: [
            {
              label: 'p Values',
              data: pData,
              borderColor: 'blue',
              fill: false,
              tension: 0.1,
              pointRadius: 0,
              showLine: true
            },
            {
              label: 's Values',
              data: sData,
              borderColor: 'red',
              fill: false,
              tension: 0.1,
              pointRadius: 0,
              showLine: true
            }
          ]
        },
        options: {
          scales: {
            x: {
              type: 'linear',
              position: 'bottom',
              title: {
                display: true,
                text: 'X Axis'
              }
            },
            y: {
              title: {
                display: true,
                text: 'Y Axis'
              }
            }
          }
        }
      });
    }

    // Fetch and plot the chart using AJAX
    fetchJSONFile('https://www.sagarrawal.com.np/csvjson.json', plotChart);
    #chart-container {
      width: 50%;
      height: 90%;
      position: relative;
    }

    canvas {
      background-color: white;
    }

    /* Draggable vertical line */
    #draggable {
      position: absolute;
      width: 2px;
      height: 100%;
      background-color: black;
      z-index: 10;
      cursor: pointer;
    }

    /* Tooltip to show values */
    #tooltip {
      position: absolute;
      background-color: rgba(0, 0, 0, 0.75);
      color: white;
      padding: 5px;
      border-radius: 3px;
      font-size: 12px;
      display: none;
      z-index: 20;
    }
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

<div id="chart-container">
    <div id="draggable"></div>
    <canvas id="myChart"></canvas>
    <div id="tooltip"></div> <!-- Tooltip to display values -->
  </div>

It runs and achives its goals but with error cause by this line
‘draggableElement.style.height = ${newHeight}px;’ , which is understandable as it has not been defined anywhere in the code, so when i remove the line, the black line slider then appears on top of the plot outside the two plots. But Only keeping it , the plot behaves as expected and when dragged black line appears within the two plot. so though i get results the way i want but i’m not able to understand why removing the above line , my chart don’t work as expected.

Audio Recording Javascript Chrome extension working with Youtube but not working on Google Meet Mic to record audio

I have fully working code which starts, stop recording audio from a tab. It also plays and download the most recent recorded audio. I tested it both Youtube and Google Meet. It captures the speaker output but when I try to capture mic audio from Google Meet, it fails. What should I be modifying in the code so that it captures both mic as well speaker audio?

Manifest.json

{
  "name": "Audio Recorder",
  "description": "Records audio (speaker + Mic) of current tab in an offscreen document.",
  "version": "1",
  "manifest_version": 3,
  "minimum_chrome_version": "116",
  "action": {
    "default_popup": "popup.html",
    "default_icon": "not-recording.png"
  },
  "permissions": ["offscreen", "contextMenus", "activeTab", "tabCapture", "tabs", "storage", "cookies"]
}

offscreen.js

chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
  if (message.target === 'offscreen') {
    switch (message.type) {
      case 'start-recording':
        startRecording(message.data);
        break;
      case 'stop-recording':
        stopRecording();
        break;
      case 'play-audio':
        playAudio();
        break;
      case 'download-audio':
        downloadAudio();
        break;
      default:
        throw new Error('Unrecognized message:', message.type);
    }
  } else if (message.type === 'check-audio') {
    sendResponse({ audioAvailable: audioBlobs.length > 0 });
    return true;
  }
});

let recorder;
let data = [];
let audioBlobs = []; // Array to store audio blobs

async function startRecording(streamId) {
  if (recorder?.state === 'recording') {
    throw new Error('Called startRecording while recording is in progress.');
  }

  // Request tab audio
  const tabAudio = await navigator.mediaDevices.getUserMedia({
    audio: {
      mandatory: {
        chromeMediaSource: 'tab',
        chromeMediaSourceId: streamId
      }
    }
  });

  let micAudio;
  try {
    // Attempt to request microphone audio
    micAudio = await navigator.mediaDevices.getUserMedia({
      audio: true // Request microphone audio
    });
  } catch (error) {
    console.warn('Microphone not available or permission denied:', error);
    micAudio = null; // Set micAudio to null if not available
  }

  // Combine the audio streams if microphone is available
  const combinedStream = micAudio 
    ? new MediaStream([...tabAudio.getAudioTracks(), ...micAudio.getAudioTracks()])
    : tabAudio; // Use only tab audio if mic is not available

  const output = new AudioContext();
  const source = output.createMediaStreamSource(combinedStream);
  source.connect(output.destination);

  recorder = new MediaRecorder(combinedStream, { mimeType: 'audio/webm' });
  recorder.ondataavailable = (event) => data.push(event.data);
  recorder.onstop = () => {
    const blob = new Blob(data, { type: 'audio/webm' });
    audioBlobs.push(blob); // Store the blob in the array
    data = [];
  };
  recorder.start();

  window.location.hash = 'recording';
}

async function stopRecording() {
  recorder.stop();
  recorder.stream.getTracks().forEach((t) => t.stop());
  window.location.hash = '';
}

function playAudio() {
  if (audioBlobs.length > 0) {
    const lastBlob = audioBlobs[audioBlobs.length - 1];
    const audioUrl = URL.createObjectURL(lastBlob);
    const audio = new Audio(audioUrl);
    audio.play();
  } else {
    console.error('No audio available to play.');
  }
}

function downloadAudio() {
  if (audioBlobs.length > 0) {
    const lastBlob = audioBlobs[audioBlobs.length - 1];
    const audioUrl = URL.createObjectURL(lastBlob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = audioUrl;
    a.download = 'recording.webm';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  } else {
    console.error('No audio available to download.');
  }
}

popup.js

document.addEventListener('DOMContentLoaded', () => {
  const isRecording = localStorage.getItem('isRecording') === 'true';

  document.getElementById('startButton').disabled = isRecording;
  document.getElementById('stopButton').disabled = !isRecording;

  // Check if there are any audio recordings stored
  chrome.runtime.sendMessage({ type: 'check-audio' }, (response) => {
    const audioAvailable = response.audioAvailable;
    document.getElementById('playButton').disabled = !audioAvailable;
    document.getElementById('downloadButton').disabled = !audioAvailable;
  });
});

document.getElementById('startButton').addEventListener('click', async () => {
  document.getElementById('startButton').disabled = true;
  document.getElementById('stopButton').disabled = false;
  document.getElementById('playButton').disabled = true;
  document.getElementById('downloadButton').disabled = true;

  localStorage.setItem('isRecording', 'true');

  const existingContexts = await chrome.runtime.getContexts({});
  let offscreenDocument = existingContexts.find(
    (c) => c.contextType === 'OFFSCREEN_DOCUMENT'
  );

  if (!offscreenDocument) {
    await chrome.offscreen.createDocument({
      url: 'offscreen.html',
      reasons: ['USER_MEDIA'],
      justification: 'Recording from chrome.tabCapture API'
    });
  }

  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  const streamId = await chrome.tabCapture.getMediaStreamId({ targetTabId: tab.id });

  chrome.runtime.sendMessage({
    type: 'start-recording',
    target: 'offscreen',
    data: streamId
  });
});

document.getElementById('stopButton').addEventListener('click', () => {
  document.getElementById('startButton').disabled = false;
  document.getElementById('stopButton').disabled = true;

  chrome.runtime.sendMessage({
    type: 'stop-recording',
    target: 'offscreen'
  }, () => {
    chrome.runtime.sendMessage({ type: 'check-audio' }, (response) => {
      const audioAvailable = response.audioAvailable;
      document.getElementById('playButton').disabled = !audioAvailable;
      document.getElementById('downloadButton').disabled = !audioAvailable;
    });
  });

  localStorage.setItem('isRecording', 'false');
});

document.getElementById('playButton').addEventListener('click', () => {
  chrome.runtime.sendMessage({
    type: 'play-audio',
    target: 'offscreen'
  });
});

document.getElementById('downloadButton').addEventListener('click', () => {
  chrome.runtime.sendMessage({
    type: 'download-audio',
    target: 'offscreen'
  });
});