react-native-simple-epub-reader 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/lib/module/components/GestureHandler/index.js +8 -2
  2. package/lib/module/components/GestureHandler/index.js.map +1 -1
  3. package/lib/module/components/Reader.js +98 -18
  4. package/lib/module/components/Reader.js.map +1 -1
  5. package/lib/module/constants/template.js +74 -70
  6. package/lib/module/constants/template.js.map +1 -1
  7. package/lib/module/constants/theme.js +28 -11
  8. package/lib/module/constants/theme.js.map +1 -1
  9. package/lib/module/helpers/downloadEpub.js +0 -3
  10. package/lib/module/helpers/downloadEpub.js.map +1 -1
  11. package/lib/module/helpers/saveTemplateToFile.js +4 -0
  12. package/lib/module/helpers/saveTemplateToFile.js.map +1 -1
  13. package/lib/module/hooks/useInjectWebviewVariables.js +4 -2
  14. package/lib/module/hooks/useInjectWebviewVariables.js.map +1 -1
  15. package/lib/typescript/src/components/GestureHandler/index.d.ts.map +1 -1
  16. package/lib/typescript/src/components/Reader.d.ts +1 -1
  17. package/lib/typescript/src/components/Reader.d.ts.map +1 -1
  18. package/lib/typescript/src/constants/template.d.ts +1 -1
  19. package/lib/typescript/src/constants/template.d.ts.map +1 -1
  20. package/lib/typescript/src/constants/theme.d.ts.map +1 -1
  21. package/lib/typescript/src/helpers/downloadEpub.d.ts.map +1 -1
  22. package/lib/typescript/src/helpers/saveTemplateToFile.d.ts +1 -0
  23. package/lib/typescript/src/helpers/saveTemplateToFile.d.ts.map +1 -1
  24. package/lib/typescript/src/hooks/useInjectWebviewVariables.d.ts +2 -1
  25. package/lib/typescript/src/hooks/useInjectWebviewVariables.d.ts.map +1 -1
  26. package/lib/typescript/src/types/index.d.ts +4 -0
  27. package/lib/typescript/src/types/index.d.ts.map +1 -1
  28. package/package.json +1 -1
  29. package/src/components/GestureHandler/index.tsx +8 -0
  30. package/src/components/Reader.tsx +125 -17
  31. package/src/constants/template.ts +74 -70
  32. package/src/constants/theme.ts +28 -11
  33. package/src/helpers/downloadEpub.ts +0 -2
  34. package/src/helpers/saveTemplateToFile.ts +5 -0
  35. package/src/hooks/useInjectWebviewVariables.ts +9 -0
  36. package/src/types/index.ts +4 -0
@@ -1,4 +1,4 @@
1
1
  import { type ReaderProps } from '../types';
2
- declare const Reader: ({ src, onTap, onSwipeLeft, onSwipeRight, initialLocation, onLocationsReady, onLocationChange, onFinish, onBeginning, onPinch, LoaderComponent, onWebViewMessage, }: ReaderProps) => import("react/jsx-runtime").JSX.Element;
2
+ declare const Reader: ({ src, onTap, onSwipeLeft, onSwipeRight, initialLocation, beginAt, waitForLocationsReady, onLocationsReady, onLocationChange, onFinish, onBeginning, onPinch, LoaderComponent, onWebViewMessage, }: ReaderProps) => import("react/jsx-runtime").JSX.Element;
3
3
  export default Reader;
4
4
  //# sourceMappingURL=Reader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Reader.d.ts","sourceRoot":"","sources":["../../../../src/components/Reader.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAwBxD,QAAA,MAAM,MAAM,GAAI,oKAab,WAAW,4CAmQb,CAAC;AAkBF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Reader.d.ts","sourceRoot":"","sources":["../../../../src/components/Reader.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AA4BxD,QAAA,MAAM,MAAM,GAAI,oMAeb,WAAW,4CAwWb,CAAC;AAmBF,eAAe,MAAM,CAAC"}
@@ -1,3 +1,3 @@
1
- declare const _default: "\n<!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>EPUB.js</title>\n <script id=\"jszip\"></script>\n <script id=\"epubjs\"></script>\n\n <style type=\"text/css\">\n body {\n margin: 0;\n background-color: #211F26;\n }\n\n #viewer {\n height: 100vh;\n width: 100vw;\n overflow: hidden !important;\n display: flex;\n justify-content: center;\n align-items: center;\n }\n\n [ref=\"epubjs-mk-balloon\"] {\n background: url(\"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPScxLjEnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZycgeG1sbnM6eGxpbms9J2h0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsnIHg9JzBweCcgeT0nMHB4JyB2aWV3Qm94PScwIDAgNzUgNzUnPjxnIGZpbGw9JyNCREJEQkQnIGlkPSdidWJibGUnPjxwYXRoIGNsYXNzPSdzdDAnIGQ9J00zNy41LDkuNEMxOS42LDkuNCw1LDIwLjUsNSwzNC4zYzAsNS45LDIuNywxMS4zLDcuMSwxNS42TDkuNiw2NS42bDE5LTcuM2MyLjgsMC42LDUuOCwwLjksOC45LDAuOSBDNTUuNSw1OS4yLDcwLDQ4LjEsNzAsMzQuM0M3MCwyMC41LDU1LjQsOS40LDM3LjUsOS40eicvPjwvZz48L3N2Zz4=\") no-repeat;\n width: 20px;\n height: 20px;\n cursor: pointer;\n margin-left: 0;\n }\n\n [ref=\"epubjs-mk-heart\"] {\n background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACOUlEQVR4nLWUTWgTURDH14Oe9JiPNqFNujvvzdsm3bdvPxKMFUEPag/iwdaD3j1JDymlCMXiqUeRHvWgFRQUxKPirUU8eFARvCnUj7QXP7DiJtk8easJjRjzIQ784bEz82Pe7MzTtP9tpmnu8UbNpOM4uzvFKF+3GM1BHHIAbwjA7xyY5AaGPuCarZtHmzGcsGM+YevKp2JUrAN4XeW2wSxKMy6wrSkKtbsiJZ96SfnAGZbl8bG6DawhdLwqAK9xYI25XLaufCrmjkjJKQpVF3DLzrDRFtAHXJ9hUNsoxOTH8hn5afGcrBRjkR66w3I/0GoJaPWRO9T63tRGISanmVHzgK1FMBvGmSr/iZeUn5fL8svlRbl5aKQt6bGXjPQ7bKefA5MOIahZOpsuAQmUY3t1pWNSN5WABtwwT2kW4Mki0OqgoMov+YA1rrMTmk3IhCr3hd/5St303EtEV54Yw5xq4y4PcHOFt/etH12xRqQHWFGsn/MFuHAQaPCmGO8b9roQl5OEBpaB862xoZTuc4F+uJDLhv0CF/LZ0DPoe9M097YNNwd2hAMLb9rpnmGrdlr1LrQJO/zH9bMMnBWA4X0n1RV2T6TU6oUc2Pm/vQ0aN/CSAKzfFp0rvWWnI5gNbEnrxWwD59UOL+UzjXc7ftTbYlxezGca0X4Dm+sJ1jQO7LgA/Hoa9eCln5Cv/IQ8i3ogAL+pZdAGMYcQdAGfHSAkmCQkUOc8pXQgWNPUgysAl5XU+Z9gg9gPaBjV+CGbZVoAAAAASUVORK5CYII=\") no-repeat;\n width: 20px;\n height: 20px;\n cursor: pointer;\n margin-left: 0;\n }\n </style>\n </head>\n\n <body oncopy='return false' oncut='return false'>\n <div id=\"viewer\"></div>\n\n <script>\n let book;\n let rendition;\n\n const type = window.type;\n const file = window.book;\n const theme = window.theme;\n const initialLocations = window.locations;\n const enableSelection = window.enable_selection;\n const allowScriptedContent = window.allowScriptedContent || false;\n const allowPopups = window.allowPopups || false;\n\n if (!file) {\n const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView !== null ? window.ReactNativeWebView : window;\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onDisplayError\",\n reason: \"Book file is missing\"\n }));\n }\n\n if (type === 'epub' || type === 'opf' || type === 'binary') {\n book = ePub(file);\n } else if (type === 'base64') {\n book = ePub(file, { encoding: \"base64\" });\n } else {\n const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView !== null ? window.ReactNativeWebView : window;\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onDisplayError\",\n reason: \"Missing or invalid file type\"\n }));\n }\n\n rendition = book.renderTo(\"viewer\", {\n width: \"100%\",\n height: \"100%\",\n manager: \"default\",\n flow: \"auto\",\n snap: undefined,\n spread: undefined,\n fullsize: undefined,\n allowPopups: allowPopups,\n allowScriptedContent: allowScriptedContent\n });\n \n const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView!== null ? window.ReactNativeWebView: window;\n reactNativeWebview.postMessage(JSON.stringify({ type: \"onStarted\" }));\n\n function flatten(chapters) {\n return [].concat.apply([], chapters.map((chapter) => [].concat.apply([chapter], flatten(chapter.subitems))));\n }\n\n function getCfiFromHref(book, href) {\n const [_, id] = href.split('#')\n let section = book.spine.get(href.split('/')[1]) || book.spine.get(href) || book.spine.get(href.split('/').slice(1).join('/'))\n\n const el = (id ? section.document.getElementById(id) : section.document.body)\n return section.cfiFromElement(el)\n }\n\n function getChapter(location) {\n const locationHref = location.start.href\n\n let match = flatten(book.navigation.toc)\n .filter((chapter) => {\n return book.canonical(chapter.href).includes(locationHref)\n }, null)\n .reduce((result, chapter) => {\n const locationAfterChapter = ePub.CFI.prototype.compare(location.start.cfi, getCfiFromHref(book, chapter.href)) > 0\n return locationAfterChapter ? chapter : result\n }, null);\n\n return match;\n };\n\n const makeRangeCfi = (a, b) => {\n const CFI = new ePub.CFI()\n const start = CFI.parse(a), end = CFI.parse(b)\n const cfi = {\n range: true,\n base: start.base,\n path: {\n steps: [],\n terminal: null\n },\n start: start.path,\n end: end.path\n }\n const len = cfi.start.steps.length\n for (let i = 0; i < len; i++) {\n if (CFI.equalStep(cfi.start.steps[i], cfi.end.steps[i])) {\n if (i == len - 1) {\n // Last step is equal, check terminals\n if (cfi.start.terminal === cfi.end.terminal) {\n // CFI's are equal\n cfi.path.steps.push(cfi.start.steps[i])\n // Not a range\n cfi.range = false\n }\n } else cfi.path.steps.push(cfi.start.steps[i])\n } else break\n }\n cfi.start.steps = cfi.start.steps.slice(cfi.path.steps.length)\n cfi.end.steps = cfi.end.steps.slice(cfi.path.steps.length)\n\n return 'epubcfi(' + CFI.segmentString(cfi.base)\n + '!' + CFI.segmentString(cfi.path)\n + ',' + CFI.segmentString(cfi.start)\n + ',' + CFI.segmentString(cfi.end)\n + ')'\n }\n\n if (!enableSelection) {\n rendition.themes.default({\n 'body': {\n '-webkit-touch-callout': 'none', /* iOS Safari */\n '-webkit-user-select': 'none', /* Safari */\n '-khtml-user-select': 'none', /* Konqueror HTML */\n '-moz-user-select': 'none', /* Firefox */\n '-ms-user-select': 'none', /* Internet Explorer/Edge */\n 'user-select': 'none'\n }\n });\n }\n\n book.ready\n .then(function () {\n if (initialLocations) {\n book.locations.load(initialLocations);\n }\n return rendition.display();\n })\n .then(function () {\n var currentLocation = rendition.currentLocation();\n\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onReady\",\n totalLocations: book.locations.total,\n currentLocation: currentLocation,\n progress: currentLocation?.start?.cfi\n ? book.locations.percentageFromCfi(currentLocation.start.cfi)\n : 0,\n }));\n\n if (initialLocations) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onLocationsReady\",\n epubKey: book.key(),\n locations: initialLocations,\n totalLocations: book.locations.total,\n currentLocation: currentLocation,\n progress: currentLocation?.start?.cfi\n ? book.locations.percentageFromCfi(currentLocation.start.cfi)\n : 0,\n }));\n return Promise.resolve();\n }\n\n return book.locations.generate(1600).then(function () {\n var generatedLocation = rendition.currentLocation() || currentLocation;\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onLocationsReady\",\n epubKey: book.key(),\n locations: book.locations.save(),\n totalLocations: book.locations.total,\n currentLocation: generatedLocation,\n progress: generatedLocation?.start?.cfi\n ? book.locations.percentageFromCfi(generatedLocation.start.cfi)\n : 0,\n }));\n }).catch(function () {\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onLocationsReady\",\n epubKey: book.key(),\n locations: [],\n totalLocations: book.locations.total,\n currentLocation: currentLocation,\n progress: 0,\n }));\n });\n\n book\n .coverUrl()\n .then(async (url) => {\n var reader = new FileReader();\n reader.onload = (res) => {\n reactNativeWebview.postMessage(\n JSON.stringify({\n type: \"meta\",\n metadata: {\n cover: reader.result,\n author: book.package.metadata.creator,\n title: book.package.metadata.title,\n description: book.package.metadata.description,\n language: book.package.metadata.language,\n publisher: book.package.metadata.publisher,\n rights: book.package.metadata.rights,\n },\n })\n );\n };\n reader.readAsDataURL(await fetch(url).then((res) => res.blob()));\n })\n .catch(() => {\n reactNativeWebview.postMessage(\n JSON.stringify({\n type: \"meta\",\n metadata: {\n cover: undefined,\n author: book.package.metadata.creator,\n title: book.package.metadata.title,\n description: book.package.metadata.description,\n language: book.package.metadata.language,\n publisher: book.package.metadata.publisher,\n rights: book.package.metadata.rights,\n },\n })\n );\n });\n\n book.loaded.navigation.then(function (item) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: 'onNavigationLoaded',\n toc: item.toc,\n landmarks: item.landmarks\n }));\n });\n })\n .catch(function (err) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onDisplayError\",\n reason: err.message || err.toString()\n }));\n });\n\n let isAnimating = false;\n const originalNext = rendition.next.bind(rendition);\n const originalPrev = rendition.prev.bind(rendition);\n\n rendition.next = function() {\n if (isAnimating) return;\n isAnimating = true;\n \n const container = rendition.manager.container;\n container.style.transition = 'opacity 0.2s ease-out';\n container.style.opacity = '0.4';\n \n setTimeout(() => {\n originalNext();\n setTimeout(() => {\n container.style.opacity = '1';\n setTimeout(() => {\n container.style.transition = '';\n isAnimating = false;\n }, 200);\n }, 50);\n }, 100);\n };\n\n rendition.prev = function() {\n if (isAnimating) return;\n isAnimating = true;\n \n const container = rendition.manager.container;\n container.style.transition = 'opacity 0.2s ease-out';\n container.style.opacity = '0.4';\n \n setTimeout(() => {\n originalPrev();\n setTimeout(() => {\n container.style.opacity = '1';\n setTimeout(() => {\n container.style.transition = '';\n isAnimating = false;\n }, 200);\n }, 50);\n }, 100);\n };\n\n rendition.on('started', () => {\n rendition.themes.register({ theme: theme });\n rendition.themes.select('theme');\n });\n\n rendition.on(\"relocated\", function (location) {\n var percent = book.locations.percentageFromCfi(location.start.cfi);\n var percentage = Math.floor(percent * 100);\n var chapter = getChapter(location);\n\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onLocationChange\",\n totalLocations: book.locations.total,\n currentLocation: location,\n progress: percentage,\n currentSection: chapter,\n }));\n\n if (location.atStart) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onBeginning\",\n }));\n }\n\n if (location.atEnd) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onFinish\",\n }));\n }\n });\n\n rendition.on(\"orientationchange\", function (orientation) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: 'onOrientationChange',\n orientation: orientation\n }));\n });\n\n rendition.on(\"rendered\", function (section) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: 'onRendered',\n section: section,\n currentSection: book.navigation.get(section.href),\n }));\n });\n\n rendition.on(\"layout\", function (layout) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: 'onLayout',\n layout: layout,\n }));\n });\n\n rendition.on(\"selected\", function (cfiRange, contents) {\n book.getRange(cfiRange).then(function (range) {\n if (range) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: 'onSelected',\n cfiRange: cfiRange,\n text: range.toString(),\n }));\n }\n });\n });\n\n rendition.on(\"resized\", function (layout) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: 'onResized',\n layout: layout,\n }));\n });\n </script>\n </body>\n</html>\n";
1
+ declare const _default: "\n<!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>EPUB.js</title>\n <script id=\"jszip\"></script>\n <script id=\"epubjs\"></script>\n\n <style type=\"text/css\">\n body {\n margin: 0;\n background-color: #211F26;\n }\n\n #viewer {\n height: 100vh;\n width: 100vw;\n overflow: hidden !important;\n display: flex;\n justify-content: center;\n align-items: center;\n }\n\n [ref=\"epubjs-mk-balloon\"] {\n background: url(\"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPScxLjEnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZycgeG1sbnM6eGxpbms9J2h0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsnIHg9JzBweCcgeT0nMHB4JyB2aWV3Qm94PScwIDAgNzUgNzUnPjxnIGZpbGw9JyNCREJEQkQnIGlkPSdidWJibGUnPjxwYXRoIGNsYXNzPSdzdDAnIGQ9J00zNy41LDkuNEMxOS42LDkuNCw1LDIwLjUsNSwzNC4zYzAsNS45LDIuNywxMS4zLDcuMSwxNS42TDkuNiw2NS42bDE5LTcuM2MyLjgsMC42LDUuOCwwLjksOC45LDAuOSBDNTUuNSw1OS4yLDcwLDQ4LjEsNzAsMzQuM0M3MCwyMC41LDU1LjQsOS40LDM3LjUsOS40eicvPjwvZz48L3N2Zz4=\") no-repeat;\n width: 20px;\n height: 20px;\n cursor: pointer;\n margin-left: 0;\n }\n\n [ref=\"epubjs-mk-heart\"] {\n background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACOUlEQVR4nLWUTWgTURDH14Oe9JiPNqFNujvvzdsm3bdvPxKMFUEPag/iwdaD3j1JDymlCMXiqUeRHvWgFRQUxKPirUU8eFARvCnUj7QXP7DiJtk8easJjRjzIQ784bEz82Pe7MzTtP9tpmnu8UbNpOM4uzvFKF+3GM1BHHIAbwjA7xyY5AaGPuCarZtHmzGcsGM+YevKp2JUrAN4XeW2wSxKMy6wrSkKtbsiJZ96SfnAGZbl8bG6DawhdLwqAK9xYI25XLaufCrmjkjJKQpVF3DLzrDRFtAHXJ9hUNsoxOTH8hn5afGcrBRjkR66w3I/0GoJaPWRO9T63tRGISanmVHzgK1FMBvGmSr/iZeUn5fL8svlRbl5aKQt6bGXjPQ7bKefA5MOIahZOpsuAQmUY3t1pWNSN5WABtwwT2kW4Mki0OqgoMov+YA1rrMTmk3IhCr3hd/5St303EtEV54Yw5xq4y4PcHOFt/etH12xRqQHWFGsn/MFuHAQaPCmGO8b9roQl5OEBpaB862xoZTuc4F+uJDLhv0CF/LZ0DPoe9M097YNNwd2hAMLb9rpnmGrdlr1LrQJO/zH9bMMnBWA4X0n1RV2T6TU6oUc2Pm/vQ0aN/CSAKzfFp0rvWWnI5gNbEnrxWwD59UOL+UzjXc7ftTbYlxezGca0X4Dm+sJ1jQO7LgA/Hoa9eCln5Cv/IQ8i3ogAL+pZdAGMYcQdAGfHSAkmCQkUOc8pXQgWNPUgysAl5XU+Z9gg9gPaBjV+CGbZVoAAAAASUVORK5CYII=\") no-repeat;\n width: 20px;\n height: 20px;\n cursor: pointer;\n margin-left: 0;\n }\n </style>\n </head>\n\n <body oncopy='return false' oncut='return false'>\n <div id=\"viewer\"></div>\n\n <script>\n let book;\n let rendition;\n\n const type = window.type;\n const file = window.book;\n const theme = window.theme;\n const initialLocations = window.locations;\n const enableSelection = window.enable_selection;\n const allowScriptedContent = window.allowScriptedContent || false;\n const allowPopups = window.allowPopups || false;\n const LOCATION_GENERATION_CHARS = 2800;\n\n if (!file) {\n const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView !== null ? window.ReactNativeWebView : window;\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onDisplayError\",\n reason: \"Book file is missing\"\n }));\n }\n\n if (type === 'epub' || type === 'opf' || type === 'binary') {\n book = ePub(file);\n } else if (type === 'base64') {\n book = ePub(file, { encoding: \"base64\" });\n } else {\n const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView !== null ? window.ReactNativeWebView : window;\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onDisplayError\",\n reason: \"Missing or invalid file type\"\n }));\n }\n\n rendition = book.renderTo(\"viewer\", {\n width: \"100%\",\n height: \"100%\",\n manager: \"default\",\n flow: \"auto\",\n snap: undefined,\n spread: undefined,\n fullsize: undefined,\n allowPopups: allowPopups,\n allowScriptedContent: allowScriptedContent\n });\n \n const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView!== null ? window.ReactNativeWebView: window;\n reactNativeWebview.postMessage(JSON.stringify({ type: \"onStarted\" }));\n\n function flatten(chapters) {\n return [].concat.apply([], chapters.map((chapter) => [].concat.apply([chapter], flatten(chapter.subitems))));\n }\n\n function getCfiFromHref(book, href) {\n const [_, id] = href.split('#')\n let section = book.spine.get(href.split('/')[1]) || book.spine.get(href) || book.spine.get(href.split('/').slice(1).join('/'))\n\n const el = (id ? section.document.getElementById(id) : section.document.body)\n return section.cfiFromElement(el)\n }\n\n function getChapter(location) {\n const locationHref = location.start.href\n\n let match = flatten(book.navigation.toc)\n .filter((chapter) => {\n return book.canonical(chapter.href).includes(locationHref)\n }, null)\n .reduce((result, chapter) => {\n const locationAfterChapter = ePub.CFI.prototype.compare(location.start.cfi, getCfiFromHref(book, chapter.href)) > 0\n return locationAfterChapter ? chapter : result\n }, null);\n\n return match;\n };\n\n const makeRangeCfi = (a, b) => {\n const CFI = new ePub.CFI()\n const start = CFI.parse(a), end = CFI.parse(b)\n const cfi = {\n range: true,\n base: start.base,\n path: {\n steps: [],\n terminal: null\n },\n start: start.path,\n end: end.path\n }\n const len = cfi.start.steps.length\n for (let i = 0; i < len; i++) {\n if (CFI.equalStep(cfi.start.steps[i], cfi.end.steps[i])) {\n if (i == len - 1) {\n // Last step is equal, check terminals\n if (cfi.start.terminal === cfi.end.terminal) {\n // CFI's are equal\n cfi.path.steps.push(cfi.start.steps[i])\n // Not a range\n cfi.range = false\n }\n } else cfi.path.steps.push(cfi.start.steps[i])\n } else break\n }\n cfi.start.steps = cfi.start.steps.slice(cfi.path.steps.length)\n cfi.end.steps = cfi.end.steps.slice(cfi.path.steps.length)\n\n return 'epubcfi(' + CFI.segmentString(cfi.base)\n + '!' + CFI.segmentString(cfi.path)\n + ',' + CFI.segmentString(cfi.start)\n + ',' + CFI.segmentString(cfi.end)\n + ')'\n }\n\n if (!enableSelection) {\n rendition.themes.default({\n 'body': {\n '-webkit-touch-callout': 'none', /* iOS Safari */\n '-webkit-user-select': 'none', /* Safari */\n '-khtml-user-select': 'none', /* Konqueror HTML */\n '-moz-user-select': 'none', /* Firefox */\n '-ms-user-select': 'none', /* Internet Explorer/Edge */\n 'user-select': 'none'\n }\n });\n }\n\n book.ready\n .then(function () {\n if (initialLocations) {\n book.locations.load(initialLocations);\n }\n return rendition.display();\n })\n .then(function () {\n var currentLocation = rendition.currentLocation();\n\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onReady\",\n totalLocations: book.locations.total,\n currentLocation: currentLocation,\n progress: currentLocation?.start?.cfi\n ? book.locations.percentageFromCfi(currentLocation.start.cfi)\n : 0,\n }));\n\n // Defer heavier work to let the onReady bridge message be delivered first.\n setTimeout(function () {\n if (initialLocations && initialLocations.length) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onLocationsReady\",\n epubKey: book.key(),\n locations: initialLocations,\n totalLocations: book.locations.total,\n currentLocation: currentLocation,\n progress: currentLocation?.start?.cfi\n ? book.locations.percentageFromCfi(currentLocation.start.cfi)\n : 0,\n }));\n } else {\n // Larger chunk size reduces startup cost for the first locations map.\n book.locations.generate(LOCATION_GENERATION_CHARS).then(function () {\n var generatedLocation = rendition.currentLocation() || currentLocation;\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onLocationsReady\",\n epubKey: book.key(),\n locations: book.locations.save(),\n totalLocations: book.locations.total,\n currentLocation: generatedLocation,\n progress: generatedLocation?.start?.cfi\n ? book.locations.percentageFromCfi(generatedLocation.start.cfi)\n : 0,\n }));\n }).catch(function () {\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onLocationsReady\",\n epubKey: book.key(),\n locations: [],\n totalLocations: book.locations.total,\n currentLocation: currentLocation,\n progress: 0,\n }));\n });\n }\n\n book\n .coverUrl()\n .then(async (url) => {\n var reader = new FileReader();\n reader.onload = () => {\n reactNativeWebview.postMessage(\n JSON.stringify({\n type: \"meta\",\n metadata: {\n cover: reader.result,\n author: book.package.metadata.creator,\n title: book.package.metadata.title,\n description: book.package.metadata.description,\n language: book.package.metadata.language,\n publisher: book.package.metadata.publisher,\n rights: book.package.metadata.rights,\n },\n })\n );\n };\n reader.readAsDataURL(await fetch(url).then((res) => res.blob()));\n })\n .catch(() => {\n reactNativeWebview.postMessage(\n JSON.stringify({\n type: \"meta\",\n metadata: {\n cover: undefined,\n author: book.package.metadata.creator,\n title: book.package.metadata.title,\n description: book.package.metadata.description,\n language: book.package.metadata.language,\n publisher: book.package.metadata.publisher,\n rights: book.package.metadata.rights,\n },\n })\n );\n });\n\n book.loaded.navigation.then(function (item) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: 'onNavigationLoaded',\n toc: item.toc,\n landmarks: item.landmarks\n }));\n });\n }, 0);\n })\n .catch(function (err) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onDisplayError\",\n reason: err.message || err.toString()\n }));\n });\n\n let isAnimating = false;\n const originalNext = rendition.next.bind(rendition);\n const originalPrev = rendition.prev.bind(rendition);\n\n rendition.next = function() {\n if (isAnimating) return;\n isAnimating = true;\n \n const container = rendition.manager.container;\n container.style.transition = 'opacity 0.2s ease-out';\n container.style.opacity = '0.4';\n \n setTimeout(() => {\n originalNext();\n setTimeout(() => {\n container.style.opacity = '1';\n setTimeout(() => {\n container.style.transition = '';\n isAnimating = false;\n }, 200);\n }, 50);\n }, 100);\n };\n\n rendition.prev = function() {\n if (isAnimating) return;\n isAnimating = true;\n \n const container = rendition.manager.container;\n container.style.transition = 'opacity 0.2s ease-out';\n container.style.opacity = '0.4';\n \n setTimeout(() => {\n originalPrev();\n setTimeout(() => {\n container.style.opacity = '1';\n setTimeout(() => {\n container.style.transition = '';\n isAnimating = false;\n }, 200);\n }, 50);\n }, 100);\n };\n\n rendition.on('started', () => {\n rendition.themes.register({ theme: theme });\n rendition.themes.select('theme');\n });\n\n rendition.on(\"relocated\", function (location) {\n var percent = book.locations.percentageFromCfi(location.start.cfi);\n var percentage = Math.floor(percent * 100);\n var chapter = getChapter(location);\n\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onLocationChange\",\n totalLocations: book.locations.total,\n currentLocation: location,\n progress: percentage,\n currentSection: chapter,\n }));\n\n if (location.atStart) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onBeginning\",\n }));\n }\n\n if (location.atEnd) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: \"onFinish\",\n }));\n }\n });\n\n rendition.on(\"orientationchange\", function (orientation) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: 'onOrientationChange',\n orientation: orientation\n }));\n });\n\n rendition.on(\"rendered\", function (section) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: 'onRendered',\n section: section,\n currentSection: book.navigation.get(section.href),\n }));\n });\n\n rendition.on(\"layout\", function (layout) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: 'onLayout',\n layout: layout,\n }));\n });\n\n rendition.on(\"selected\", function (cfiRange, contents) {\n book.getRange(cfiRange).then(function (range) {\n if (range) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: 'onSelected',\n cfiRange: cfiRange,\n text: range.toString(),\n }));\n }\n });\n });\n\n rendition.on(\"resized\", function (layout) {\n reactNativeWebview.postMessage(JSON.stringify({\n type: 'onResized',\n layout: layout,\n }));\n });\n </script>\n </body>\n</html>\n";
2
2
  export default _default;
3
3
  //# sourceMappingURL=template.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../../../src/constants/template.ts"],"names":[],"mappings":";AAAA,wBA8YE"}
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../../../src/constants/template.ts"],"names":[],"mappings":";AAAA,wBAkZE"}
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../../src/constants/theme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEtC,eAAO,MAAM,YAAY,EAAE,KAuC1B,CAAC"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../../src/constants/theme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEtC,eAAO,MAAM,YAAY,EAAE,KAwD1B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"downloadEpub.d.ts","sourceRoot":"","sources":["../../../../src/helpers/downloadEpub.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,GACvB,KAAK,MAAM,EACX,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,CAuBhB,CAAC"}
1
+ {"version":3,"file":"downloadEpub.d.ts","sourceRoot":"","sources":["../../../../src/helpers/downloadEpub.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,GACvB,KAAK,MAAM,EACX,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,CAqBhB,CAAC"}
@@ -1,3 +1,4 @@
1
1
  export declare const saveTemplateToFile: (template: string, fileName: string) => string;
2
2
  export declare const checkTemplateFileExists: (fileName: string) => boolean;
3
+ export declare const getTemplateFileUri: (fileName: string) => string;
3
4
  //# sourceMappingURL=saveTemplateToFile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"saveTemplateToFile.d.ts","sourceRoot":"","sources":["../../../../src/helpers/saveTemplateToFile.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,EAAE,UAAU,MAAM,WAYpE,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,UAAU,MAAM,YAGvD,CAAC"}
1
+ {"version":3,"file":"saveTemplateToFile.d.ts","sourceRoot":"","sources":["../../../../src/helpers/saveTemplateToFile.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,EAAE,UAAU,MAAM,WAYpE,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,UAAU,MAAM,YAGvD,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,WAGlD,CAAC"}
@@ -1,12 +1,13 @@
1
1
  import type { SourceType, Theme } from '../types';
2
2
  export declare function useInjectWebViewVariables(): {
3
- injectWebViewVariables: ({ jszip, epubjs, type, book, allowScriptedContent, theme, }: {
3
+ injectWebViewVariables: ({ jszip, epubjs, type, book, allowScriptedContent, theme, locations, }: {
4
4
  jszip: string;
5
5
  epubjs: string;
6
6
  type: SourceType;
7
7
  book: string;
8
8
  allowScriptedContent?: boolean;
9
9
  theme: Theme;
10
+ locations?: string[];
10
11
  }) => string;
11
12
  };
12
13
  //# sourceMappingURL=useInjectWebviewVariables.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useInjectWebviewVariables.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useInjectWebviewVariables.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAGlD,wBAAgB,yBAAyB;0FASlC;QACD,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAC/B,KAAK,EAAE,KAAK,CAAC;KACd;EA6BJ"}
1
+ {"version":3,"file":"useInjectWebviewVariables.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useInjectWebviewVariables.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAGlD,wBAAgB,yBAAyB;qGAUlC;QACD,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,UAAU,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAC/B,KAAK,EAAE,KAAK,CAAC;QACb,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;KACtB;EAoCJ"}
@@ -57,12 +57,16 @@ export interface GestureHandlerProps {
57
57
  onSwipeLeft?: () => void;
58
58
  onSwipeRight?: () => void;
59
59
  onTap?: () => void;
60
+ onPinchStart?: () => void;
60
61
  onPinch?: (e: GestureUpdateEvent<PinchGestureHandlerEventPayload>) => void;
62
+ onPinchEnd?: () => void;
61
63
  onWebViewMessage?: (event: any) => void;
62
64
  }
63
65
  export type ReaderProps = {
64
66
  src: string;
65
67
  initialLocation?: string;
68
+ beginAt?: number;
69
+ waitForLocationsReady?: boolean;
66
70
  onLocationChange?: (data: LocationChangeData) => void;
67
71
  onLocationsReady?: (epubKey: string, locations: ePubCfi[]) => void;
68
72
  onFinish?: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,+BAA+B,EAChC,MAAM,8BAA8B,CAAC;AAEtC,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,QAAQ,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,OAAO,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE;QACH,GAAG,EAAE,OAAO,CAAC;QACb,SAAS,EAAE;YACT,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;QACF,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,KAAK,EAAE;QACL,GAAG,EAAE,OAAO,CAAC;QACb,SAAS,EAAE;YACT,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;QACF,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,QAAQ,EAAE,GAAG,EAAE,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B,oBAAY,UAAU;IACpB,IAAI,SAAS;CACd;AAED,MAAM,MAAM,KAAK,GAAG;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG;QACb,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;KACvB,CAAC;CACH,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,kBAAkB,CAAC,+BAA+B,CAAC,KAAK,IAAI,CAAC;IAC3E,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;CACzC;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACtD,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACnE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CACvC,GAAG,mBAAmB,CAAC;AAExB,MAAM,MAAM,IAAI,GAAG,WAAW,GAAG,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,+BAA+B,EAChC,MAAM,8BAA8B,CAAC;AAEtC,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,QAAQ,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,OAAO,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE;QACH,GAAG,EAAE,OAAO,CAAC;QACb,SAAS,EAAE;YACT,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;QACF,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,KAAK,EAAE;QACL,GAAG,EAAE,OAAO,CAAC;QACb,SAAS,EAAE;YACT,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;QACF,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,QAAQ,EAAE,GAAG,EAAE,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B,oBAAY,UAAU;IACpB,IAAI,SAAS;CACd;AAED,MAAM,MAAM,KAAK,GAAG;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG;QACb,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;KACvB,CAAC;CACH,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,kBAAkB,CAAC,+BAA+B,CAAC,KAAK,IAAI,CAAC;IAC3E,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;CACzC;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACtD,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACnE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CACvC,GAAG,mBAAmB,CAAC;AAExB,MAAM,MAAM,IAAI,GAAG,WAAW,GAAG,cAAc,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-simple-epub-reader",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Simple ePub renderer for React Native",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -13,7 +13,9 @@ const GestureHandler: FC<PropsWithChildren<GestureHandlerProps>> = ({
13
13
  onSwipeLeft,
14
14
  onSwipeRight,
15
15
  onTap,
16
+ onPinchStart,
16
17
  onPinch,
18
+ onPinchEnd,
17
19
  }) => {
18
20
  const swipeLeftGesture = Gesture.Fling()
19
21
  .runOnJS(true)
@@ -38,8 +40,14 @@ const GestureHandler: FC<PropsWithChildren<GestureHandlerProps>> = ({
38
40
 
39
41
  const pinchGesture = Gesture.Pinch()
40
42
  .runOnJS(true)
43
+ .onStart(() => {
44
+ onPinchStart?.();
45
+ })
41
46
  .onUpdate((e) => {
42
47
  onPinch?.(e);
48
+ })
49
+ .onEnd(() => {
50
+ onPinchEnd?.();
43
51
  });
44
52
 
45
53
  const combinedGesture = Gesture.Race(
@@ -9,7 +9,11 @@ import {
9
9
  useState,
10
10
  } from 'react';
11
11
  import { loadScripts } from '../helpers/loadScripts';
12
- import { saveTemplateToFile } from '../helpers/saveTemplateToFile';
12
+ import {
13
+ checkTemplateFileExists,
14
+ getTemplateFileUri,
15
+ saveTemplateToFile,
16
+ } from '../helpers/saveTemplateToFile';
13
17
  import { downloadEpub } from '../helpers/downloadEpub';
14
18
  import { Paths } from 'expo-file-system';
15
19
  import { useInjectWebViewVariables } from '../hooks/useInjectWebviewVariables';
@@ -29,6 +33,8 @@ const Reader = ({
29
33
  onSwipeLeft,
30
34
  onSwipeRight,
31
35
  initialLocation,
36
+ beginAt,
37
+ waitForLocationsReady = false,
32
38
  onLocationsReady = () => {},
33
39
  onLocationChange = () => {},
34
40
  onFinish = () => {},
@@ -38,7 +44,15 @@ const Reader = ({
38
44
  onWebViewMessage,
39
45
  }: ReaderProps) => {
40
46
  const [templateUri, setTemplateUri] = useState<string>('');
41
- // const [isReaderReady, setIsReaderReady] = useState(false);
47
+ const pinchStartFontSizeRef = useRef<number | null>(null);
48
+ const hasAppliedBeginAtRef = useRef(false);
49
+ const templateAssetsRef = useRef<{
50
+ jszip: string;
51
+ epubjs: string;
52
+ localEpubUri: string;
53
+ } | null>(null);
54
+ const hasPersistedLocationsRef = useRef(false);
55
+ const hasLocationsReadyRef = useRef(false);
42
56
 
43
57
  const {
44
58
  registerBook,
@@ -61,6 +75,30 @@ const Reader = ({
61
75
  const { injectWebViewVariables } = useInjectWebViewVariables();
62
76
  const bookRef = useRef<WebView | null>(null);
63
77
 
78
+ const goToProgress = useCallback((progress: number) => {
79
+ const normalizedProgress =
80
+ progress > 1
81
+ ? Math.min(Math.max(progress / 100, 0), 1)
82
+ : Math.min(Math.max(progress, 0), 1);
83
+
84
+ bookRef.current?.injectJavaScript(`
85
+ if (
86
+ typeof rendition !== 'undefined' &&
87
+ rendition &&
88
+ typeof book !== 'undefined' &&
89
+ book &&
90
+ book.locations &&
91
+ typeof book.locations.cfiFromPercentage === 'function'
92
+ ) {
93
+ const targetCfi = book.locations.cfiFromPercentage(${normalizedProgress});
94
+ if (targetCfi) {
95
+ rendition.display(targetCfi);
96
+ }
97
+ }
98
+ true;
99
+ `);
100
+ }, []);
101
+
64
102
  const onMessage = (event: any) => {
65
103
  let parsedEvent;
66
104
  try {
@@ -70,8 +108,6 @@ const Reader = ({
70
108
  return;
71
109
  }
72
110
 
73
- console.log(parsedEvent.type);
74
-
75
111
  if (!INTERNAL_EVENTS.includes(parsedEvent?.type) && onWebViewMessage) {
76
112
  return onWebViewMessage(parsedEvent);
77
113
  }
@@ -101,15 +137,60 @@ const Reader = ({
101
137
  break;
102
138
  case 'onLocationsReady':
103
139
  const props = parsedEvent;
140
+ hasLocationsReadyRef.current = true;
104
141
  setLocations(parsedEvent.locations);
105
142
  setTotalLocations(props.totalLocations);
106
143
  setCurrentLocation(props.currentLocation);
107
144
  setProgress(props.progress);
145
+ setIsLoading(false);
108
146
 
109
- return onLocationsReady(props.epubKey, parsedEvent.locations);
147
+ if (
148
+ typeof beginAt === 'number' &&
149
+ !initialLocation &&
150
+ !hasAppliedBeginAtRef.current
151
+ ) {
152
+ goToProgress(beginAt);
153
+ hasAppliedBeginAtRef.current = true;
154
+ }
155
+
156
+ onLocationsReady(props.epubKey, parsedEvent.locations);
157
+
158
+ if (
159
+ parsedEvent.locations?.length &&
160
+ templateAssetsRef.current &&
161
+ !hasPersistedLocationsRef.current
162
+ ) {
163
+ setTimeout(() => {
164
+ const assets = templateAssetsRef.current;
165
+ if (!assets) return;
166
+
167
+ try {
168
+ const generatedTemplateWithLocations = injectWebViewVariables({
169
+ jszip: assets.jszip,
170
+ epubjs: assets.epubjs,
171
+ type: SourceType.EPUB,
172
+ allowScriptedContent: true,
173
+ book: assets.localEpubUri,
174
+ theme: initialTheme,
175
+ locations: parsedEvent.locations,
176
+ });
177
+
178
+ saveTemplateToFile(
179
+ generatedTemplateWithLocations,
180
+ htmlTemplateName
181
+ );
182
+ hasPersistedLocationsRef.current = true;
183
+ } catch (persistError) {
184
+ console.warn('Failed to persist cached locations:', persistError);
185
+ }
186
+ }, 0);
187
+ }
188
+
189
+ break;
110
190
  case 'onReady':
111
- // setIsReaderReady(true);
112
- setIsLoading(false);
191
+ if (!waitForLocationsReady) {
192
+ setIsLoading(false);
193
+ }
113
194
  if (initialLocation) {
114
195
  goToLocation(initialLocation);
115
196
  }
@@ -139,13 +220,11 @@ const Reader = ({
139
220
  };
140
221
 
141
222
  const handleOnSwipeLeft = () => {
142
- // if (!isReaderReady) return;
143
223
  onSwipeLeft?.();
144
224
  goNext();
145
225
  };
146
226
 
147
227
  const handleOnSwipeRight = () => {
148
- // if (!isReaderReady) return;
149
228
  onSwipeRight?.();
150
229
  goPrevious();
151
230
  };
@@ -153,17 +232,27 @@ const Reader = ({
153
232
  const handleOnPinch = (
154
233
  e: GestureUpdateEvent<PinchGestureHandlerEventPayload>
155
234
  ) => {
156
- // if (!isReaderReady) return;
235
+ if (pinchStartFontSizeRef.current === null) {
236
+ pinchStartFontSizeRef.current = parseFloat(fontSize.replace('pt', ''));
237
+ }
157
238
 
158
- const fontSizeValue = parseInt(fontSize.replace('pt', ''), 10);
239
+ const baseFontSize = pinchStartFontSizeRef.current;
240
+ const sensitivity = 0.5;
241
+ const adjustedScale = 1 + (e.scale - 1) * sensitivity;
242
+ const scaledFontSize = baseFontSize * adjustedScale;
243
+ const clampedFontSize = Math.min(Math.max(scaledFontSize, 9), 32);
244
+ const smoothedFontSize = Math.round(clampedFontSize * 2) / 2;
159
245
 
160
- const scaleValue = e.scale > 1 ? e.scale * 0.5 : e.scale;
246
+ changeFontSize(`${smoothedFontSize}pt`);
247
+ onPinch?.(e);
248
+ };
161
249
 
162
- const newFontSize = fontSizeValue * scaleValue;
250
+ const handleOnPinchStart = () => {
251
+ pinchStartFontSizeRef.current = parseFloat(fontSize.replace('pt', ''));
252
+ };
163
253
 
164
- const clampedFontSize = Math.min(Math.max(newFontSize, 6), 32);
165
- changeFontSize(`${clampedFontSize}pt`);
166
- onPinch?.(e);
254
+ const handleOnPinchEnd = () => {
255
+ pinchStartFontSizeRef.current = null;
167
256
  };
168
257
 
169
258
  const epubFileName = useMemo(() => {
@@ -201,8 +290,10 @@ const Reader = ({
201
290
  const prepareReader = async () => {
202
291
  try {
203
292
  setIsLoading(true);
204
- // setIsReaderReady(false);
205
293
  setTemplateUri('');
294
+ hasAppliedBeginAtRef.current = false;
295
+ hasPersistedLocationsRef.current = false;
296
+ hasLocationsReadyRef.current = false;
206
297
 
207
298
  const [, jszip, epubjs] = await loadScripts();
208
299
 
@@ -210,6 +301,15 @@ const Reader = ({
210
301
 
211
302
  const localEpubUri = await downloadEpub(src, epubFileName);
212
303
 
304
+ templateAssetsRef.current = { jszip, epubjs, localEpubUri };
305
+
306
+ if (checkTemplateFileExists(htmlTemplateName)) {
307
+ if (isMounted) {
308
+ setTemplateUri(getTemplateFileUri(htmlTemplateName));
309
+ }
310
+ return;
311
+ }
312
+
213
313
  const generatedTemplate = injectWebViewVariables({
214
314
  jszip,
215
315
  epubjs,
@@ -261,7 +361,9 @@ const Reader = ({
261
361
  onSwipeLeft={handleOnSwipeLeft}
262
362
  onSwipeRight={handleOnSwipeRight}
263
363
  onTap={handleOnTap}
364
+ onPinchStart={handleOnPinchStart}
264
365
  onPinch={handleOnPinch}
366
+ onPinchEnd={handleOnPinchEnd}
265
367
  >
266
368
  <WebView
267
369
  pointerEvents="none"
@@ -278,6 +380,11 @@ const Reader = ({
278
380
  allowFileAccess
279
381
  javaScriptCanOpenWindowsAutomatically
280
382
  onMessage={onMessage}
383
+ onLoadEnd={() => {
384
+ if (!waitForLocationsReady || hasLocationsReadyRef.current) {
385
+ setIsLoading(false);
386
+ }
387
+ }}
281
388
  style={styles.container}
282
389
  />
283
390
  </GestureHandler>
@@ -310,6 +417,7 @@ const styles = StyleSheet.create({
310
417
  ...StyleSheet.absoluteFillObject,
311
418
  justifyContent: 'center',
312
419
  alignItems: 'center',
420
+ backgroundColor: '#ffffff',
313
421
  },
314
422
  });
315
423
 
@@ -55,6 +55,7 @@ export default `
55
55
  const enableSelection = window.enable_selection;
56
56
  const allowScriptedContent = window.allowScriptedContent || false;
57
57
  const allowPopups = window.allowPopups || false;
58
+ const LOCATION_GENERATION_CHARS = 2800;
58
59
 
59
60
  if (!file) {
60
61
  const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView !== null ? window.ReactNativeWebView : window;
@@ -187,53 +188,73 @@ export default `
187
188
  : 0,
188
189
  }));
189
190
 
190
- if (initialLocations) {
191
- reactNativeWebview.postMessage(JSON.stringify({
192
- type: "onLocationsReady",
193
- epubKey: book.key(),
194
- locations: initialLocations,
195
- totalLocations: book.locations.total,
196
- currentLocation: currentLocation,
197
- progress: currentLocation?.start?.cfi
198
- ? book.locations.percentageFromCfi(currentLocation.start.cfi)
199
- : 0,
200
- }));
201
- return Promise.resolve();
202
- }
203
-
204
- return book.locations.generate(1600).then(function () {
205
- var generatedLocation = rendition.currentLocation() || currentLocation;
206
- reactNativeWebview.postMessage(JSON.stringify({
207
- type: "onLocationsReady",
208
- epubKey: book.key(),
209
- locations: book.locations.save(),
210
- totalLocations: book.locations.total,
211
- currentLocation: generatedLocation,
212
- progress: generatedLocation?.start?.cfi
213
- ? book.locations.percentageFromCfi(generatedLocation.start.cfi)
214
- : 0,
215
- }));
216
- }).catch(function () {
217
- reactNativeWebview.postMessage(JSON.stringify({
218
- type: "onLocationsReady",
219
- epubKey: book.key(),
220
- locations: [],
221
- totalLocations: book.locations.total,
222
- currentLocation: currentLocation,
223
- progress: 0,
224
- }));
225
- });
226
-
227
- book
228
- .coverUrl()
229
- .then(async (url) => {
230
- var reader = new FileReader();
231
- reader.onload = (res) => {
191
+ // Defer heavier work to let the onReady bridge message be delivered first.
192
+ setTimeout(function () {
193
+ if (initialLocations && initialLocations.length) {
194
+ reactNativeWebview.postMessage(JSON.stringify({
195
+ type: "onLocationsReady",
196
+ epubKey: book.key(),
197
+ locations: initialLocations,
198
+ totalLocations: book.locations.total,
199
+ currentLocation: currentLocation,
200
+ progress: currentLocation?.start?.cfi
201
+ ? book.locations.percentageFromCfi(currentLocation.start.cfi)
202
+ : 0,
203
+ }));
204
+ } else {
205
+ // Larger chunk size reduces startup cost for the first locations map.
206
+ book.locations.generate(LOCATION_GENERATION_CHARS).then(function () {
207
+ var generatedLocation = rendition.currentLocation() || currentLocation;
208
+ reactNativeWebview.postMessage(JSON.stringify({
209
+ type: "onLocationsReady",
210
+ epubKey: book.key(),
211
+ locations: book.locations.save(),
212
+ totalLocations: book.locations.total,
213
+ currentLocation: generatedLocation,
214
+ progress: generatedLocation?.start?.cfi
215
+ ? book.locations.percentageFromCfi(generatedLocation.start.cfi)
216
+ : 0,
217
+ }));
218
+ }).catch(function () {
219
+ reactNativeWebview.postMessage(JSON.stringify({
220
+ type: "onLocationsReady",
221
+ epubKey: book.key(),
222
+ locations: [],
223
+ totalLocations: book.locations.total,
224
+ currentLocation: currentLocation,
225
+ progress: 0,
226
+ }));
227
+ });
228
+ }
229
+
230
+ book
231
+ .coverUrl()
232
+ .then(async (url) => {
233
+ var reader = new FileReader();
234
+ reader.onload = () => {
235
+ reactNativeWebview.postMessage(
236
+ JSON.stringify({
237
+ type: "meta",
238
+ metadata: {
239
+ cover: reader.result,
240
+ author: book.package.metadata.creator,
241
+ title: book.package.metadata.title,
242
+ description: book.package.metadata.description,
243
+ language: book.package.metadata.language,
244
+ publisher: book.package.metadata.publisher,
245
+ rights: book.package.metadata.rights,
246
+ },
247
+ })
248
+ );
249
+ };
250
+ reader.readAsDataURL(await fetch(url).then((res) => res.blob()));
251
+ })
252
+ .catch(() => {
232
253
  reactNativeWebview.postMessage(
233
254
  JSON.stringify({
234
255
  type: "meta",
235
256
  metadata: {
236
- cover: reader.result,
257
+ cover: undefined,
237
258
  author: book.package.metadata.creator,
238
259
  title: book.package.metadata.title,
239
260
  description: book.package.metadata.description,
@@ -243,33 +264,16 @@ export default `
243
264
  },
244
265
  })
245
266
  );
246
- };
247
- reader.readAsDataURL(await fetch(url).then((res) => res.blob()));
248
- })
249
- .catch(() => {
250
- reactNativeWebview.postMessage(
251
- JSON.stringify({
252
- type: "meta",
253
- metadata: {
254
- cover: undefined,
255
- author: book.package.metadata.creator,
256
- title: book.package.metadata.title,
257
- description: book.package.metadata.description,
258
- language: book.package.metadata.language,
259
- publisher: book.package.metadata.publisher,
260
- rights: book.package.metadata.rights,
261
- },
262
- })
263
- );
264
- });
265
-
266
- book.loaded.navigation.then(function (item) {
267
- reactNativeWebview.postMessage(JSON.stringify({
268
- type: 'onNavigationLoaded',
269
- toc: item.toc,
270
- landmarks: item.landmarks
271
- }));
272
- });
267
+ });
268
+
269
+ book.loaded.navigation.then(function (item) {
270
+ reactNativeWebview.postMessage(JSON.stringify({
271
+ type: 'onNavigationLoaded',
272
+ toc: item.toc,
273
+ landmarks: item.landmarks
274
+ }));
275
+ });
276
+ }, 0);
273
277
  })
274
278
  .catch(function (err) {
275
279
  reactNativeWebview.postMessage(JSON.stringify({