From ffbceda69d3d788df7020361d3d6b5f26ad9a999 Mon Sep 17 00:00:00 2001 From: Steve McConnel Date: Thu, 21 Nov 2024 11:57:22 -0700 Subject: [PATCH] fix: allow nonASCII page numbers for book urls (BL-13882) --- src/bloom-player-controls.tsx | 8 ++++++-- src/utilities/math.test.ts | 18 +++++++++++++++++- src/utilities/mathUtils.ts | 31 +++++++++++++++++++++++++++++++ tsconfig.json | 2 +- 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/bloom-player-controls.tsx b/src/bloom-player-controls.tsx index acc5813f..7e462ccf 100644 --- a/src/bloom-player-controls.tsx +++ b/src/bloom-player-controls.tsx @@ -32,7 +32,7 @@ import { withStyles, createTheme } from "@material-ui/core/styles"; import DragBar from "@material-ui/core/Slider"; import { bloomRed } from "./bloomPlayerTheme"; import { setDurationOfPagesWithoutNarration } from "./narration"; -import { roundToNearestK } from "./utilities/mathUtils"; +import { roundToNearestK, normalizeDigits } from "./utilities/mathUtils"; // This component is designed to wrap a BloomPlayer with some controls // for things like pausing audio and motion, hiding and showing @@ -1055,6 +1055,7 @@ function getExtraButtons(): IExtraButton[] { return []; } } + // a bit goofy...we need some way to get react called when this code is loaded into an HTML // document (as part of bloomPlayerControlBundle.js). When that module is loaded, any // not-in-a-class code gets called. So we arrange in bloom-player-root.ts to call this @@ -1068,7 +1069,10 @@ export function InitBloomPlayerControls() { "motion", )! as autoPlayType; const startPageString = getQueryStringParamAndUnencode("start-page"); - const startPage = startPageString ? parseInt(startPageString) : undefined; + const decimalStartPageString = normalizeDigits(startPageString); + const startPage = decimalStartPageString + ? parseInt(decimalStartPageString) + : undefined; const autoplayCountString = getQueryStringParamAndUnencode("autoplay-count"); const autoplayCount = startPageString diff --git a/src/utilities/math.test.ts b/src/utilities/math.test.ts index c65758f4..e82bb5b5 100644 --- a/src/utilities/math.test.ts +++ b/src/utilities/math.test.ts @@ -1,4 +1,4 @@ -import { roundToNearestK } from "./mathUtils"; +import { roundToNearestK, normalizeDigits } from "./mathUtils"; const roundToNearestEvenCases = [ [0, 0], @@ -31,3 +31,19 @@ test.each(roundToNearest4Cases)("roundToNearestK, k=4", (input, expected) => { const result = roundToNearestK(input, 4); expect(result).toEqual(expected); }); + +test("normalizeDigits from mathUtils.ts", () => { + expect(normalizeDigits("1234567890")).toEqual("1234567890"); + expect(normalizeDigits("١٢٣٤٥٦٧٨٩٠")).toEqual("1234567890"); + expect(normalizeDigits("०१२३४५६७८९")).toEqual("0123456789"); + expect(normalizeDigits("٦٧٨٩٠١٢٣٤٥")).toEqual("6789012345"); + expect(normalizeDigits("१२३४५६७८९०١٢٣٤٥٦٧٨٩٠")).toEqual( + "12345678901234567890", + ); + expect(normalizeDigits("١٢٣٤٥٦٧٨٩٠cover")).toEqual("1234567890cover"); + expect(normalizeDigits("test")).toEqual("test"); + expect(normalizeDigits("cover")).toBeUndefined(); // special case + expect(normalizeDigits("")).toBeUndefined(); + expect(normalizeDigits(undefined)).toBeUndefined(); + expect(normalizeDigits(null)).toBeUndefined(); +}); diff --git a/src/utilities/mathUtils.ts b/src/utilities/mathUtils.ts index b1f95e0c..3fc5156e 100644 --- a/src/utilities/mathUtils.ts +++ b/src/utilities/mathUtils.ts @@ -8,3 +8,34 @@ export function roundToNearestEven(x: number): number { export function roundToNearestK(x: number, k: number): number { return Math.round(x / k) * k; } + +// This may not be a math function, strictly speaking, but it's at least +// math adjacent. +/** + * Convert all Unicode digits in a string to ASCII digits. + */ +// adapted from https://stackoverflow.com/questions/17024985/javascript-cant-convert-hindi-arabic-numbers-to-real-numeric-variables +export function normalizeDigits(str): string | undefined { + if (!str || str === "cover") { + return undefined; // not a number we can parse + } + if (/^\d+$/.test(str)) { + return str; // entire string is ASCII decimal digits + } + try { + // find all characters which are DecimalNumber (property Nd), except for ASCII 0-9 + return str.replace(/(?![0-9])\p{Nd}/gu, (g) => { + // all Nd blocks start at 0x...0 or end at 0x...F (and starts at 0x...6) + // if it starts at 0x...0, the ASCII decimal number is (i & 0xf) + // if it ends at 0x...F, the ASCII decimal number is (i & 0xf) - 6 + // we recognize the 2 cases by testing if code | 0xf == 0x...F is still a decimal number + const code = g.charCodeAt(0); + return ( + (code & 0xf) - + 6 * (/\p{Nd}/u.test(String.fromCodePoint(code | 0xf)) ? 1 : 0) + ); + }); + } catch (e) { + return undefined; + } +} diff --git a/tsconfig.json b/tsconfig.json index 8774b6a3..ea124d76 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es6", "module": "commonjs", "sourceMap": true, "jsx": "react",