nutpi-chinese-number-format: A powerful UTS plugin for formatting Chinese numbers (Compatible with HarmonyOS)

Preface

In mobile application development, the localized display of numbers is a common requirement. Especially in a Chinese environment, we often need to convert Arabic numerals to Chinese numerals or perform the opposite conversion. Today, I would like to introduce to you a powerful UTS plugin – nutpi-chinese-number-format, which is specifically designed to address various requirements for formatting Chinese numbers.

Plugin Overview

nutpi-chinese-number-format is a Chinese number formatting plugin specifically designed for the uni-app and uni-app x projects. It is developed based on UTS (UniApp TypeScript) technology and provides complete TypeScript type definitions to ensure type safety during the development process.

Core characteristics

  • Bidirectional conversion: Supports bidirectional conversion between Arabic numerals and Chinese numerals
  • Uppercase support: Supports conversion of Chinese numerals to uppercase format (e.g. One, Two, three)
  • Unit processing: Intelligent processing of Chinese digital units such as tens of thousands and billions
  • Approximate representation: Supports approximate Chinese representation of large numbers
  • Month conversion: Supports conversion from numeric months to Chinese months (including traditional month names)
  • ulti-region support: Supports both Simplified Chinese (zh-CN) and Traditional Chinese (zh-TW)
  • Cross-platform compatibility: Supports multiple platforms including App (Android/iOS/Harmony), H5 and mini programs
  • Type safety: Complete TypeScript type definitions

Development process

We need to create the project in the way shown in the figure first, and then select uniappx and vue3
enter image description here

Next, we can create a new uni_modules plugin. Among them, we select the uts plugin -API plugin.

enter image description here

The following is the process of everyone writing code. It should be noted here that since the connection between this plugin and the platform is not very strong, we just need to create index.uts in the root directory of the plugin to implement my logic.

Technical architecture

Technical advantages of UTS

This plugin is developed using UTS (UniApp TypeScript) technology and has the following advantages compared with traditional JavaScript plugins:

  1. Performance optimization: The performance of the code compiled by UTS is closer to native
  2. Type safety: Full TypeScript support to reduce runtime errors
  3. Cross-platform consistency: maintaining consistent behavior across different platforms
  4. Development experience: Better IDE support and code hints

Core algorithm design

The plugin adopts an efficient mapping table design internally:

// Basic number mapping
const NUMBER_MAPS = {
  base: {
    "0": ["0", "0", "零", "〇"],
    "1": ["1", "1", "一", "壹"],
    "2": ["2", "2", "二", "貳", "贰"],
    // ...
  },
"zh-TW": {
    units: ["", "十", "百", "千"],
    bigUnits: [
      "",
      "萬",
      "億",
      "兆",
      "京",
      "垓",
      "秭",
      "穰",
      "溝",
      "澗",
      "正",
      "載",
    ],
    point: "點",
    uppercase: {
      /* Traditional uppercase mapping */
    },
  },
"zh-CN": {
    units: ["", "十", "百", "千"],
    bigUnits: [
      "",
      "万",
      "亿",
      "兆",
      "京",
      "垓",
      "秭",
      "穰",
      "沟",
      "涧",
      "正",
      "载",
    ],
    point: "点",
    uppercase: {
      /* Simplified Capital Letter mapping */
    },
  },
};

To optimize performance, the plugin also expects to calculate the reverse mapping table:

// Pre-calculate the reverse mapping table to optimize the performance of the toNumber function
const REVERSE_BASE_MAP: Record<string, string> = {};
for (const numKey in NUMBER_MAPS.base) {
  NUMBER_MAPS.base[numKey].forEach((charVariant) => {
    REVERSE_BASE_MAP[charVariant] = numKey;
  });
}

Detailed Explanation of Functions

1. Basic digital conversion

Digital to Chinese

import { toChinese } from "@/uni_modules/nutpi-chinese-number-format";

// Basic transformation
const result1 = toChinese(123); // "一二三"
const result2 = toChinese(123.45); // "一二三點四五"

// Designated area
const result3 = toChinese(123, "zh-CN"); // "一二三"
const result4 = toChinese(123.45, "zh-CN"); // "一二三点四五"

Chinese to digital

import { toNumber } from "@/uni_modules/nutpi-chinese-number-format";

const num1 = toNumber("一二三"); // 123
const num2 = toNumber("一二三點四五"); // 123.45
const num3 = toNumber("一萬二千三百四十五"); // 12345

2. Chinese representation with units

import { toChineseWithUnits } from "@/uni_modules/nutpi-chinese-number-format";

// Automatically add appropriate units
const result1 = toChineseWithUnits(12345); // "一萬二千三百四十五"
const result2 = toChineseWithUnits(12345, "zh-CN"); // "一万二千三百四十五"
const result3 = toChineseWithUnits(123456789); // "一億二千三百四十五萬六千七百八十九"
  1. Capitalization conversion
import { toUpperCase } from "@/uni_modules/nutpi-chinese-number-format";

// Convert to capital Chinese numerals
const result1 = toUpperCase("一二三"); // "壹貳參"
const result2 = toUpperCase("一二三", "zh-CN"); // "壹贰叁"
  1. Approximate representation of large numbers
import { toChineseApproximate } from "@/uni_modules/nutpi-chinese-number-format";

// Approximate representation of large numbers
const result1 = toChineseApproximate(123456789); // "一點二億"
const result2 = toChineseApproximate(123456789, {
  locale: "zh-CN",
  precision: 2,
}); // "一点二三亿"
  1. Month conversion
import { toChineseMonth } from "@/uni_modules/nutpi-chinese-number-format";

// Simple format
const month1 = toChineseMonth(10); // "十月"
const month2 = toChineseMonth(11); // "十一月"

// Traditional format
const month3 = toChineseMonth(1, { format: "traditional" }); // "正月"
const month4 = toChineseMonth(12, {
  format: "traditional",
  locale: "zh-CN",
}); // "The twelfth lunar month"

Actual application scenarios

  1. Price display in e-commerce applications
// Display the Chinese price on the product detail page
const price = 12888;
const chinesePrice = toChineseWithUnits(price, "zh-CN"); // "一万二千八百八十八"
  1. The amount in the financial application should be capitalized

// The amount in the invoice or receipt is capitalized
const amount = "一万二千三百四十五";
const uppercaseAmount = toUpperCase(amount, "zh-CN"); // "壹万贰仟叁佰肆拾伍"
  1. Month display in the date picker

// The month display in the traditional calendar
const months = [];
for (let i = 1; i <= 12; i++) {
  months.push(toChineseMonth(i, { format: "traditional", locale: "zh-CN" }));
}
// ["正月", "二月", "三月", ..., "腊月"]
  1. Large number display in data statistics
// Friendly display of user volume statistics
const userCount = 1234567;
const friendlyCount = toChineseApproximate(userCount, {
  locale: "zh-CN",
  precision: 1,
}); // "One hundred and twenty thousand"

A complete usage example in Vue components

<template>
  <view class="number-demo">
    <view class="section">
      <text class="title">Basic transformation</text>
      <text>Digital {{ originalNumber }} Convert to Chinese:{{ chineseNumber }}</text>
      <text>Chinese to digital:{{ convertedNumber }}</text>
    </view>

    <view class="section">
      <text class="title">With unit conversion</text>
      <text>{{ largeNumber }} → {{ chineseWithUnits }}</text>
    </view>

    <view class="section">
      <text class="title">Capitalization conversion</text>
      <text>{{ chineseText }} → {{ uppercaseText }}</text>
    </view>

    <view class="section">
      <text class="title">Month conversion</text>
      <text>{{ currentMonth }}月 → {{ chineseMonth }}</text>
      <text>Traditional format:{{ traditionalMonth }}</text>
    </view>
  </view>
</template>

<script setup lang="ts">
import {
  toChinese,
  toChineseWithUnits,
  toNumber,
  toUpperCase,
  toChineseApproximate,
  toChineseMonth,
  type Locales,
} from "@/uni_modules/nutpi-chinese-number-format";

// Responsive data
const originalNumber = ref(12345);
const largeNumber = ref(123456789);
const chineseText = ref("一二三四五");
const currentMonth = ref(10);

// computational attribute
const chineseNumber = computed(() => toChinese(originalNumber.value, "zh-CN"));

const convertedNumber = computed(() => toNumber("一二三四五"));

const chineseWithUnits = computed(() =>
  toChineseWithUnits(largeNumber.value, "zh-CN")
);

const uppercaseText = computed(() => toUpperCase(chineseText.value, "zh-CN"));

const chineseMonth = computed(() => toChineseMonth(currentMonth.value));

const traditionalMonth = computed(() =>
  toChineseMonth(currentMonth.value, {
    format: "traditional",
    locale: "zh-CN",
  })
);
</script>
  1. Pre-computed mapping table

The plugin pre-calculates the reverse mapping table during initialization, avoiding repetitive calculations at runtime:

// Pre-compute the reverse mapping table to enhance the performance of the toNumber function
const REVERSE_BASE_MAP: Record<string, string> = {};
for (const numKey in NUMBER_MAPS.base) {
  NUMBER_MAPS.base[numKey].forEach((charVariant) => {
    REVERSE_BASE_MAP[charVariant] = numKey;
  });
}
  1. Efficient string processing
    When dealing with large numbers, the plugin adopts a group processing method, which improves the conversion efficiency:
// Group and process by 4 bits to improve the efficiency of processing large numbers
const groups = numStr
  .split("")
  .reverse()
  .reduce((acc: string[][], digit: string, i: number) => {
    const groupIndex = Math.floor(i / 4);
    if (!acc[groupIndex]) acc[groupIndex] = [];
    acc[groupIndex].unshift(digit);
    return acc;
  }, []);

Error handling and boundary situations

  1. Input validation
// Error handling of the toNumber function
exportfunction toNumber(str: string): number {
let numberStr = "";
let hasInvalidChar = false;

for (const char of str) {
    const digit = REVERSE_BASE_MAP[char];
    if (digit !== undefined) {
      numberStr += digit;
    } else {
      hasInvalidChar = true;
      break;
    }
  }

if (hasInvalidChar || numberStr.length === 0) {
    returnNaN; // Return NaN when the conversion fails
  }

// Handle the situation of multiple decimal points
const parts = numberStr.split(".");
if (parts.length > 1) {
    numberStr = parts[0] + "." + parts.slice(1).join("");
  }

returnNumber(numberStr);
}
  1. Monthly verification
// Boundary check of the toChineseMonth function
export function toChineseMonth(
  month: number,
  options: MonthOptions = {}
): string {
  // Check whether the month is between 1 and 12 and is an integer
  if (month < 1 || month > 12 || !Number.isInteger(month)) {
    return ""; // An invalid month returns an empty string
  }
  // ... Other processing logics
}

Installation and configuration

1. Install through uni_modules

  1. Copy the “nutpi-chinese-number-format” folder to the “uni_modules” directory of the project
  2. Recompile the project in HBuilderX

2. Environmental requirements

  • HBuilderX: 3.6.8 or later version
  • uni-app: Supports Vue 2 and Vue 3
  • uni-app x: Fully supported
  • Platform support: App (Android/iOS/Harmony), H5, mini-programs, etc

3. TypeScript configuration

If your project uses TypeScript, the plugin provides complete type definitions:

// Type import
import type {
  Locales,
  Options,
  MonthOptions,
} from "@/uni_modules/nutpi-chinese-number-format";

// Use type
const locale: Locales = "zh-CN";
const options: Options = {
  locale: "zh-CN",
  precision: 2,
};

Best practice

1. Region Settings selection

// Automatically select the region based on the language of the user's device
const getLocale = (): Locales => {
  const systemLocale = uni.getSystemInfoSync().language;
  return systemLocale.includes("TW") || systemLocale.includes("HK")
    ? "zh-TW"
    : "zh-CN";
};

const userLocale = getLocale();
const result = toChinese(123, userLocale);

2. Error handling

// Secure digital conversion
const safeToNumber = (str: string): number | null => {
const result = toNumber(str);
returnisNaN(result) ? null : result;
};

// Usage example
const userInput = "一二三";
constnumber = safeToNumber(userInput);
if (number !== null) {
console.log(`Conversion successful: ${number}`);
} else {
console.log("Conversion failed. Please check the input format");
}

3. Performance optimization suggestions

// For scenarios with frequent calls, the results can be cached
const numberCache = new Map<string, string>();

const cachedToChinese = (num: number, locale: Locales = "zh-CN"): string => {
const key = `${num}_${locale}`;
if (numberCache.has(key)) {
    return numberCache.get(key)!;
  }

const result = toChinese(num, locale);
  numberCache.set(key, result);
return result;
};

Summary

nutpi-chinese-number-format is a comprehensive and high-performance Chinese number formatting plugin. It not only offers rich conversion functions, but also has the following advantages:

  • Advanced technology: Based on UTS technology, its performance is close to native
  • Complete functions: It covers various scenarios of Chinese digital processing
  • Type safety: Full TypeScript support
  • Cross-platform: Supports all platforms of the uni-app ecosystem
  • Easy to use: The simple API design makes it easy to get started
  • Performance optimization: Pre-computed mapping table, efficient algorithm implementation

Whether it’s developing e-commerce applications, financial systems, or other applications that require Chinese localization, this plugin can provide you with a reliable solution for formatting Chinese numbers.

Thank you, stackoverflow, for allowing my article to be displayed

Rails 7 – Duplicate javascript files are loaded into the web server

With Rails 7.2.2.1
I am using importmaps to load my javascript files. My problem is that some of the files have multiple copies with slightly different names loaded into the web server, and I cannot figure out why it is happening and how to prevent it. Below are two scenarios that I ran to illustrate the problem.
Scenario 1: Duplicate js files
My import map looks like this:

pin "application"
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin_all_from "app/javascript/controllers", under: "controllers"

pin "jquery", to: "https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"
pin "jquery-ui", to: "https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery-ui.min.js"


pin "confnosprdpicks", to: "confnosprdpicks.js"
pin "seasongameset_schedules", to: "seasongameset_schedules.js"
pin "survivorpicks", to: "survivorpicks.js"
pin "seasongameset_schedule_bets", to: "seasongameset_schedule_bets.js"
pin "gm_total_bets", to: "gm_total_bets.js"
pin "spread_bets", to: "spread_bets.js"
pin "spread_bet_teams_season_action", to: "spread_bet_teams_season_action.js"
pin "predictions", to: "predictions.js"
pin "super_bowl_bets", to: "super_bowl_bets.js"

If I start a Rails server and bring up a page, this is the list of javascript files that are loaded:

enter image description here

enter image description here

There are duplicate files for:
css and css.js
datetimes and datetimes.js
seasongameset_schedules and seasongameset_schedules-d7d77…js
spread_bets.js and spread_bets-7bc8d…js

The file contents for each pair is identical

Scenario 2: No duplicate js files Now, if I comment out only the last line in importmap.rb, so that it looks like this:

pin "application"
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin_all_from "app/javascript/controllers", under: "controllers"

pin "jquery", to: "https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"
pin "jquery-ui", to: "https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery-ui.min.js"


pin "confnosprdpicks", to: "confnosprdpicks.js"
pin "seasongameset_schedules", to: "seasongameset_schedules.js"
pin "survivorpicks", to: "survivorpicks.js"
pin "seasongameset_schedule_bets", to: "seasongameset_schedule_bets.js"
pin "gm_total_bets", to: "gm_total_bets.js"
pin "spread_bets", to: "spread_bets.js"
pin "spread_bet_teams_season_action", to: "spread_bet_teams_season_action.js"
pin "predictions", to: "predictions.js"
# pin "super_bowl_bets", to: "super_bowl_bets.js" 

…and restart the Rails server and bring up the same page, I get the following list of javascript files loaded to the web server – the duplicate files do not appear:

enter image description here
enter image description here

The duplicate javascript files are gone (the 2 application-nnn.js files are distinct, one being from app/javascript and one from app/javascript/controllers).

I don’t understand why multiple js files appear in the first scenario, but do not appear in the second scenario. The second scenario looks to me like what I would expect to see. Can anyone provide some insight? Thanks.

How can I track a variable across different sessions/users?

I’m making a NeoCities site using HTML/CSS/Javascript. PHP doesn’t seem to be supported without add-ons, so for the time being, i’m not considering that.

I want to see if I can have a button that simply says how many times it has been clicked, not just by the current user but by any user. I know that if I wanted it to show how many times a user clicked it, I could use localstorage or sessionstorage, but that does not seem to accomplish what I want to do. How can I store a variable that can be changed with an event that persists across users and sessions?

Javascript errors say variables are undefined in many instances

function RecordAction (Action,Table='Visits',Referrer='See%20page%20access%20line%20to%20see%20referrer...') {
[... some code not bugged or so it seems...]
document.getElementById('ActionRecorder').src = 'https://www.handsearseyes.fun/XXX/XXX.php?Table='+Table+'&Action='+Action+'&Referrer='+Referrer+'&TimeStamp='+TimeStamp;
}


    // Form field value changes logging function
function RecordFieldValueGiven(Field,DBTable='HexKeyboardVisits') {
RecordAction('Toggled%20Form%20Field%20%5C%22'+Field.name+'%5C%22%20to%20Value%20%5C%22'+Field.value+'%5C%22',DBTAble);
}

I get “cannot set property src on null” and the html does contain the iframe having ActionRecorder for Id, not name don’t worry on that…

and also “DBTable” is not defined while the variable is defined within the parameters…

whole site is half crashed :
https://www.handsearseyes.fun/Ears/EarTrainer/Main.php
or https://handsearseyes.fun/Ears/HexKeyboard/HexKeyboard.php
changing most field’s values won’t work…

I need validate a problem with a array because not find the solution

img

test test test tes
123 123 123 123123
123 123 123 123 123
twat what what whta
test test test tes
123 123 123 123123
123 123 123 123 123
twat what what whtatest test test tes
123 123 123 123123
123 123 123 123 123
twat what what whtatest test test tes
123 123 123 123123
123 123 123 123 123
twat what what whtatest test test tes
123 123 123 123123
123 123 123 123 123
twat what what whtatest test test tes
123 123 123 123123
123 123 123 123 123
twat what what whtatest test test tes
123 123 123 123123
123 123 123 123 123
twat what what whtatest test test tes
123 123 123 123123
123 123 123 123 123
twat what what whtatest test test tes
123 123 123 123123
123 123 123 123 123
twat what what whta

Multiple functions for click event in React

I am new to React. I am working on a simple project where clicking a +/- button will increment or decrement the date by one day but am having trouble getting the two functions to work for a click event, please see code below. I am not sure why error message ‘newDate is undefined’ keeps coming up when the variable is defined and the state has been set. If anybody has any solutions please let me know. Thanks.
*** N.b I am just working on the ‘minus’ button for now.

import React from "react";
import ReactDOM from "react-dom/client";
import { useState } from "react";

function App() {
  const date = new Date("june 21 2027");
  const [newDate, setNewDate] = useState(date);
  const [count, setCount] = useState(1);

  return (
    <div>
      {/* <div>
        <button>-</button>
        <span>Step : Step 1</span>
        <button>+</button>
      </div> */}
      <div>
        <button
          className={"minus"}
          id={1}
          onClick={function () {
            setCount(function (c) {
              return c - 1;
            });
            setNewDate(function (d) {
              d.setDate(date.getDate() - count);
              return;
            });
          }}
        >
          -
        </button>
        <span>Count : {count}</span>
        <button
          className={"plus"}
          id={2}
          onClick={function () {
            return setCount(function (c) {
              return c + 1;
            });
          }}
        >
          +
        </button>
      </div>
      <span>Today is {`${newDate.toDateString()}`}</span>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(<App />);

I have tried returning the setNewDate function into the setCount function but that didn’t work.

How can I use custom triggers to control object movement in Geometry Dash 2.2?

I’m building a custom level in Geometry Dash 2.2 and I want to create a looped movement for certain objects (like platforms floating up and down or side to side). I’ve tried using the Move trigger with easing and timing setups, but I can’t get it to loop smoothly without issues like jittering or breaking the flow.

What I’ve tried:
Applied Move triggers with Ease In Out for smoother transitions.

Tried repeating the motion using Repeat and Toggle triggers.

Grouped objects logically and timed delays between movements.

What I expected:
Smooth, continuous looping of object movement.

Clean transitions without stutters or sync issues.

The ability to stop or alter movement based on player progress.

What actually happens:
Movement sometimes becomes choppy or resets awkwardly.

Looped motion doesn’t stay consistent after a few cycles.

Objects sometimes lag behind or break sequence.

I’ve shared tutorials and experiences like this on my Geometry Dash-focused site: thegeometrydashes.com, but I couldn’t find a solid answer for this specific problem.

i have tried everything that i share above

Cannot read property ‘Type’ of undefined

Im having an issue where Im trying to set up Camera Access, but keep getting property ‘Type’ of undefined. I tried using CameraType in the export, but it also said it was undefined when I tried Using CameraType.back. My SDK and expo-camera version are up to date.

Relevant Code:
”’
import { Camera } from “expo-camera”;

const [type, setType] = useState(Camera.Constants.Type.back);

<Camera
style={styles.camera}
type={type} 
ref={setCameraRef}

/>
”’

Problem in browser back button after Keycloak authentication in React application

In my React application when user authenticates with Keycloak, and after redirecting back from Keycloak (React page refreshes), on pressing browser back button the user is not able to navigate to the previous React route and again gets redirected to Keycloak URL.
I have tried the following solutions and all of them work, but after page refreshing (redirecting to Keycloak) none of them work:

I use react-oidc-context.

Solution 1

window.onpopstate = function() {
  navigate("/component1");
}

Solution 2

window.history.pushState({}, '', "/component1");

Solution 3

window.addEventListener("popstate", () => {
  navigate("/component1");
});

I need to mention that, I have tested all the above codes with useEffect, but nothing worked.

Add dots/ellipsis to pagination

I have this working pagination, but I would like to add ellipsis inside the pagination based on the following logic (if maxPages = 3 and total amount of pages is eg. 36):

[1, 2, 3, '...', 36]                 // 1-3 is the current page
[1, '...', 10, 11, 12, '...', 36]    // 10-12 is the current page
[1, '...', 34, 35, 36]               // 34-36 is the current page

How can I modify the pages array to push the ellipsis into it in the right way?

const wrapper = document.querySelector(".wrapper");
const pagination = document.querySelector(".pagination-nav");
const items = Array.from(document.querySelectorAll(".item"));
let filteredItems = items;
let currPage = 1;

/**
 * 
 * @param {int} totalItems Total number of items to paginate
 * @param {int} currentPage Current page
 * @param {int} pageSize Number of items per page
 * @param {int} maxPages Max number of pages to display at the time
 * @returns array of pages
 */
function paginate(totalItems, currentPage = 1, pageSize = 2, maxPages = 3) {

    let totalPages = Math.ceil(totalItems / pageSize);
    if (currentPage < 1) {
        currentPage = 1;
    } else if (currentPage > totalPages) {
        currentPage = totalPages;
    }

    let startPage, endPage;
    if (totalPages <= maxPages) {
        startPage = 1;
        endPage = totalPages;
    } else {
        let maxPagesBeforeCurrentPage = Math.floor(maxPages / 2);
        let maxPagesAfterCurrentPage = Math.ceil(maxPages / 2) - 1;
        if (currentPage <= maxPagesBeforeCurrentPage) {
            startPage = 1;
            endPage = maxPages;
        } else if (currentPage + maxPagesAfterCurrentPage >= totalPages) {
            startPage = totalPages - maxPages + 1;
            endPage = totalPages;
        } else {
            startPage = currentPage - maxPagesBeforeCurrentPage;
            endPage = currentPage + maxPagesAfterCurrentPage;
        }
    }

    let startIndex = (currentPage - 1) * pageSize;
    let endIndex = Math.min(startIndex + pageSize - 1, totalItems - 1);

    let pages = Array.from(Array((endPage + 1) - startPage).keys()).map(i => startPage + i);
    
    console.log(totalItems, currentPage, pageSize, totalPages, startPage, endPage, startIndex, endIndex, pages);
    
    return {
        totalItems: totalItems,
        currentPage: currentPage,
        pageSize: pageSize,
        totalPages: totalPages,
        startPage: startPage,
        endPage: endPage,
        startIndex: startIndex,
        endIndex: endIndex,
        pages: pages
    };
}

function setHTML(items) {
    wrapper.innerHTML = ""
    pagination.innerHTML = ""
    const { totalItems, currentPage, pageSize, totalPages, startPage, endPage, startIndex, endIndex, pages } = paginate(items.length, currPage, 4, 3);

    const ul = document.createElement("ul");
    ul.classList.add('pagination');
    let paginationHTML = "";

    paginationHTML += `<li class="page-item ${currentPage === 1 ? 'disabled' : 'page-prev'}"><a class="page-link" href="#">Previous</a></li>`
    
    pages.forEach(page => {
        if (currentPage === page) {
            paginationHTML += `<li class="page page-item ${currentPage == page ? 'active' : ''}" data-page="${page}"><a class="page-link" href="#" ${currentPage == page ? 'aria-current="page"' : ''}>${page}</a></li>`;
        } else {
            paginationHTML += `<li class="page page-item" data-page="${page}"><a class="page-link" href="#">${page}</a></li>`;
        }
    })

    paginationHTML += `<li class="page-item ${currentPage === endPage ? 'disabled' : 'page-next'}"><a class="page-link" href="#">Next</a></li>`

    ul.innerHTML = paginationHTML;
    pagination.append(ul);

    const start = (currentPage - 1) * pageSize, end = currentPage * pageSize;
    items.slice(start, end).forEach(el => {
        wrapper.append(el)
    })
}

document.addEventListener('click', function (e) {
    const $this = e.target;

    if ($this.parentNode.classList.contains("page")) {
        currPage = parseInt($this.parentNode.getAttribute("data-page"));
        setHTML(filteredItems);
    }
    if ($this.parentNode.classList.contains("page-next")) {
        currPage += 1;
        setHTML(filteredItems);
    }
    if ($this.parentNode.classList.contains("page-prev")) {
        currPage -= 1;
        setHTML(filteredItems);
    }
});

setHTML(filteredItems);
.pagination {
    display: flex;
    padding-left: 0;
    list-style: none;
}

.page-link {
    display: block;
    padding: .375rem .75rem;
}

.active > .page-link {
    background-color: blue;
    color: #fff;
}
<div class="pagination-nav"></div>
<div class="wrapper">
  <div class="item">
    <h3>What is Lorem Ipsum?</h3>
    <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the
      industry's standard dummy text ever since the 1500s.</p>
  </div>
  <div class="item">
    <h3>Why do we use it?</h3>
    <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the
      industry's standard dummy text ever since the 1500s.</p>
  </div>
  <div class="item">
    <h3>Nam porta tempus elit</h3>
    <p>imperdiet dolor, sed convallis lacus sollicitudin at. Integer tempor elementum dolor a fermentum. Sed
      erat
      lorem, tempus mollis mi tincidunt, laoreet faucibus ex.</p>
  </div>
  <div class="item">
    <h3>Pellentesque a tincidunt metus</h3>
    <p>Magna adipisicing labore aute dolore. Officia tempor reprehenderit ad id sunt nisi pariatur. Minim
      ullamco
      aliqua et velit esse veniam.</p>
  </div>
  <div class="item">
    <h3>Tempor irure laboris veniam</h3>
    <p>Ut ad eiusmod adipisicing enim culpa mollit. Do ad adipisicing dolor incididunt dolor ad amet cupidatat.
      Nisi
      minim veniam cillum eu fugiat exercitation ut quis. Mollit velit adipisicing elit qui. Eu in esse elit
      culpa.</p>
  </div>
  <div class="item">
    <h3>Pellentesque a tincidunt metus</h3>
    <p>Magna adipisicing labore aute dolore. Officia tempor reprehenderit ad id sunt nisi pariatur. Minim
      ullamco
      aliqua et velit esse veniam.</p>
  </div>
  <div class="item">
    <h3>Tempor irure laboris veniam</h3>
    <p>Ut ad eiusmod adipisicing enim culpa mollit. Do ad adipisicing dolor incididunt dolor ad amet cupidatat.
      Nisi
      minim veniam cillum eu fugiat exercitation ut quis. Mollit velit adipisicing elit qui. Eu in esse elit
      culpa.</p>
  </div>
  <div class="item">
    <h3>Nostrud id commodo excepteur est</h3>
    <p>Dolore incididunt tempor consectetur deserunt proident id minim exercitation consectetur. Ipsum enim eu
      sit
      duis adipisicing id incididunt. Dolore eu eu sunt nostrud occaecat elit ipsum enim aliquip.</p>
  </div>
  <div class="item">
    <h3>Anim ipsum voluptate ex ex</h3>
    <p>Incididunt qui magna et laborum voluptate amet enim. Ex veniam ipsum sit excepteur. Amet commodo commodo
      labore ipsum ut incididunt. Ipsum et ad adipisicing dolore sit pariatur laboris.</p>
  </div>
  <div class="item">
    <h3>Nostrud id commodo excepteur est</h3>
    <p>Dolore incididunt tempor consectetur deserunt proident id minim exercitation consectetur. Ipsum enim eu
      sit
      duis adipisicing id incididunt. Dolore eu eu sunt nostrud occaecat elit ipsum enim aliquip.</p>
  </div>
  <div class="item">
    <h3>Anim ipsum voluptate ex ex</h3>
    <p>Incididunt qui magna et laborum voluptate amet enim. Ex veniam ipsum sit excepteur. Amet commodo commodo
      labore ipsum ut incididunt. Ipsum et ad adipisicing dolore sit pariatur laboris.</p>
  </div>
  <div class="item">
    <h3>Nostrud id commodo excepteur est</h3>
    <p>Dolore incididunt tempor consectetur deserunt proident id minim exercitation consectetur. Ipsum enim eu
      sit
      duis adipisicing id incididunt. Dolore eu eu sunt nostrud occaecat elit ipsum enim aliquip.</p>
  </div>
  <div class="item">
    <h3>Nostrud id commodo excepteur est</h3>
    <p>Dolore incididunt tempor consectetur deserunt proident id minim exercitation consectetur. Ipsum enim eu
      sit
      duis adipisicing id incididunt. Dolore eu eu sunt nostrud occaecat elit ipsum enim aliquip.</p>
  </div>
  <div class="item">
    <h3>Anim ipsum voluptate ex ex</h3>
    <p>Incididunt qui magna et laborum voluptate amet enim. Ex veniam ipsum sit excepteur. Amet commodo commodo
      labore ipsum ut incididunt. Ipsum et ad adipisicing dolore sit pariatur laboris.</p>
  </div>
  <div class="item">
    <h3>Anim ipsum voluptate ex ex</h3>
    <p>Incididunt qui magna et laborum voluptate amet enim. Ex veniam ipsum sit excepteur. Amet commodo commodo
      labore ipsum ut incididunt. Ipsum et ad adipisicing dolore sit pariatur laboris.</p>
  </div>
  <div class="item">
    <h3>Anim ipsum voluptate ex ex</h3>
    <p>Incididunt qui magna et laborum voluptate amet enim. Ex veniam ipsum sit excepteur. Amet commodo commodo
      labore ipsum ut incididunt. Ipsum et ad adipisicing dolore sit pariatur laboris.</p>
  </div>
  <div class="item">
    <h3>Exercitation id reprehenderit occaecat</h3>
    <p>Consequat minim in duis enim in aliqua elit et. Reprehenderit officia proident est minim fugiat nisi
      laboris
      esse ad. Qui culpa elit deserunt aliquip reprehenderit reprehenderit occaecat. Incididunt nulla irure
      magna
      eiusmod dolor. Cupidatat pariatur cupidatat consequat quis.</p>
  </div>
  <div class="item">
    <h3>Why do we use it?</h3>
    <p>Lorem Ipsum is simply dummy text of the printing and culo typesetting industry. Lorem Ipsum has been the
      industry's standard dummy text ever since the 1500s.</p>
  </div>
  <div class="item">
    <h3>Nam porta tempus elit</h3>
    <p>imperdiet dolor, sed convallis lacus sollicitudin at. Integer tempor elementum dolor a fermentum. Sed
      erat
      lorem, tempus mollis mi tincidunt, laoreet faucibus ex.</p>
  </div>
  <div class="item">
    <h3>Pellentesque a tincidunt metus</h3>
    <p>Magna adipisicing labore aute dolore. Officia hulu tempor reprehenderit ad id sunt nisi pariatur. Minim
      ullamco
      aliqua et velit esse veniam.</p>
  </div>
  <div class="item">
    <h3>Tempor irure laboris veniam</h3>
    <p>Ut ad eiusmod adipisicing enim culpa mollit. Do ad adipisicing dolor incididunt dolor ad amet cupidatat.
      Nisi
      minim veniam cillum eu fugiat exercitation ut quis. Mollit velit adipisicing elit qui. Eu in esse elit
      culpa.</p>
  </div>
  <div class="item">
    <h3>Nostrud id commodo excepteur est</h3>
    <p>Dolore incididunt tempor consectetur deserunt proident id minim exercitation consectetur. Ipsum enim eu
      sit
      duis adipisicing id incididunt. Dolore eu eu sunt nostrud occaecat elit ipsum enim aliquip.</p>
  </div>
  <div class="item">
    <h3>Anim ipsum voluptate ex ex</h3>
    <p>Incididunt qui magna et laborum voluptate amet enim. Ex veniam ipsum sit excepteur. Amet commodo commodo
      labore ipsum ut incididunt. Ipsum et ad adipisicing dolore sit pariatur laboris.</p>
  </div>
  <div class="item">
    <h3>Exercitation id reprehenderit occaecat</h3>
    <p>Consequat minim in duis enim in aliqua elit et. Reprehenderit officia proident est minim fugiat nisi
      laboris
      esse ad. Qui culpa elit deserunt aliquip reprehenderit reprehenderit occaecat. Incididunt nulla irure
      magna
      eiusmod dolor. Cupidatat pariatur cupidatat consequat quis.</p>
  </div>
</div>

Is there a way to edit multiple Ranges made within an element with the “contenteditable” attribute, without breaking them?

I am trying to edit the start and end points for multiple selection Ranges, made within a container with the “contenteditable” attribute set to “true”. However, the mere presence of the attribute causes the selections to break when edited (if more than one is made).

Here is a snippet I made to better illustrate the odd behavior:

    function clicked() {
        const container = document.getElementById("container");
        const selection = window.getSelection();
    
        for (let i = 0; i < selection.rangeCount; i++) {
            const range = selection.getRangeAt(i);
            range.setStart(range.startContainer, 0);
            range.setEnd(range.startContainer, range.startContainer.length);
        }
    }
<h2>With "contenteditable" not enabled</h2>
<div>
    <span>span1</span>
    <span>span2</span>
</div>
<br>
<h2>With "contenteditable" enabled</h2>
<div contenteditable="true">
    <span>span3</span>
    <span>span4</span>
</div>
<br>
<input type="button" onclick="clicked()" value="modify selection(s)">
<br>
<br>
<h3>Tests</h3>
<p>Whilst holding down CTRL, use your cursor to <b>partially</b> select text of:</p>
<ol>
    <li>span1 and span2, then press the button.</li>
    <p><small>(The selections expand correctly.)</small></p>
    <li>span3 and span4, then press the button.</li>
    <p><small>(The selections do not expand correctly.)</small></p>
    <li>span3, then press the button.</li>
    <p><small>(The selection expands correctly.)</small></p>
    <li>span1, span2, span3, and span4, then press the button.</li>
    <p><small>(The selections expand correctly, once again.)</small></p>
</ol>

I want the second test to behave like the others.

Also, just to be clear, I am using a Firefox-based browser, and am making multiple selections by holding down CTRL, whilst click-dragging the cursor (a feature that Chromium browsers seemingly lack).

After polling a server using setinterval, file will download but won’t open in a new tab

I am polling a server, using setInterval, until the pdf’s that I want to download are ready…Once the pdfs are ready to download, I clear the interval, and then I call a function

async function DownloadAndOpenPdf(fileId){
    const ret = await Utilities.PostFunction(DownloadReportFileURL, fileId);
    const blob = new Blob([Base64ToBinary.decodeArrayBuffer(ret.Base64EncryptedData)], { "type": ret.MimeType });
    const link = document.createElement('a');
    link.href = window.URL.createObjectURL(blob);
    link.download = ret.FileName + ret.FileExtension;
    link.click();
    window.open(link.href, "_blank");
}

This downloads the pdf, but then I get a message saying that the popup was blocked, however, if I run this function in DevTools the pdf gets downloaded and opens up in a new tab without any issue. If I call that function on a button click event then it works without any issue.
What am I missing? how do I fix this to download and open the file without the message saying that the popup was blocked after an interval has been cleared

Math lib for React Native [closed]

I’m building an app to perform calculations, and I need a library to help me with this.
I want a library to calculate derivatives, but mathjs doesn’t work well with React Native.
Is there another library I can use?

NodeJS with AstraDB consistenly returns error 400 when r/w from Netlify hosted HTML page

I’m working on my firs NodeJS page and have set up to just read and write a string of data when I push a button.
astra.js

const axios = require("axios");
const ASTRA_DB_ID = process.env.ASTRA_DB_ID;
const ASTRA_REGION = process.env.ASTRA_REGION;
const ASTRA_TOKEN = process.env.ASTRA_APP_TOKEN;
const COLLECTION = "test";
const BASE_URL = `https://${ASTRA_DB_ID}-${ASTRA_REGION}.apps.astra.datastax.com/api/rest/v2/keyspaces/default_keyspace/collections/${COLLECTION}`
const HEADERS = {
    "x-cassandra-token": ASTRA_TOKEN,
    "Content-Type": "application/json",
};
exports.handler = async function (event) {
    if (event.httpMethod === "POST") {
        const {_id, message} = JSON.parse(event.body);
        const res = await axios.put(`${BASE_URL}/${_id}`, {_id: _id, message}, {headers:HEADERS});
        return {
            statusCode:200,
            body: JSON.stringify({success:true, res:res.data}),
        };
    } else if (event.httpMethod === "GET") {
        const _id = event.queryStringParameters.id;
        const res = await axios.get(`${BASE_URL}/${_id}`, {headers:HEADERS});
        return {
            statusCode:200,
            body:JSON.stringify(res.data),
        };
    }
    return {statusCode:405, body: "Method Not Allowed"};
}

index.html

<!DOCTYPE html>
<html>
    <head>
        <title>DB TEST</title>
    </head>
    <body>
        <h1>Testing</h1>
        <button id="write-btn">Write to DB</button>
        <button id="read-btn">Read from DB</button>
        <script>
            document.getElementById("write-btn").addEventListener("click", write);
            document.getElementById("read-btn").addEventListener("click", read);
            async function write() {
            alert("test");
            try {
                await fetch('/api/astra', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({_id:'demo', message: 'Hellow from HTML!'})
                });
                alert("Message saved!");
            } catch (e) {
                alert(e)
            }
            }
            async function read() {
                const res = await fetch('/api/astra?id=demo');
                const data = await res.json();
                alert("Message: " + JSON.stringify(data));
            }
        </script>
    </body>
</html>

netlify.toml

[build]
  publish = "public"            
  functions = "functions"  

[[redirects]]
  from = "/api/*"
  to = "/.netlify/functions/:splat"
  status = 200

ASTRA_APP_TOKEN,ASTRA_REGION, and ASTRA_DB_ID are all configured correctly in Netlify environment variables to match the DB. However, whenever I click one of the 2 buttons, it returns:
Writing

(9)[ "AxiosError: Request failed with status code 400", " at settle (/var/task/node_modules/axios/dist/node/axios.cjs:2049:12)", " at IncomingMessage.handleStreamEnd (/var/task/node_modules/axios/dist/node/axios.cjs:3166:11)", " at IncomingMessage.emit (node:events:530:35)", " at endReadableNT (node:internal/streams/readable:1698:12)", " at process.processTicksAndRejections (node:internal/process/task_queues:90:21)", " at Axios.request (/var/task/node_modules/axios/dist/node/axios.cjs:4276:41)", " at process.processTicksAndRejections (node:internal/process/task_queues:105:5)", " at async exports.handler (/var/task/functions/astra.js:14:21)" ]

Reading

(9)[ "AxiosError: Request failed with status code 400", " at settle (/var/task/node_modules/axios/dist/node/axios.cjs:2049:12)", " at IncomingMessage.handleStreamEnd (/var/task/node_modules/axios/dist/node/axios.cjs:3166:11)", " at IncomingMessage.emit (node:events:530:35)", " at endReadableNT (node:internal/streams/readable:1698:12)", " at process.processTicksAndRejections (node:internal/process/task_queues:90:21)", " at Axios.request (/var/task/node_modules/axios/dist/node/axios.cjs:4276:41)", " at process.processTicksAndRejections (node:internal/process/task_queues:105:5)", " at async exports.handler (/var/task/functions/astra.js:21:21)" ]

Both the same error with the exception of returning different lines at which the error occurred, since, of course, reading and writing happens at different lines. Inside the DB I have a default_keyspace labeled as test. The requests are sent, but error when they try to actually fetch/put the data. Anybody know how to fix this?