react-native-simple-epub-reader 0.1.2 → 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 +125 -22
  4. package/lib/module/components/Reader.js.map +1 -1
  5. package/lib/module/constants/template.js +189 -226
  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 +5 -1
  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 +161 -27
  31. package/src/constants/template.ts +189 -226
  32. package/src/constants/theme.ts +28 -11
  33. package/src/helpers/downloadEpub.ts +7 -1
  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 +1 @@
1
- {"version":3,"names":["defaultTheme","background","color"],"sourceRoot":"../../../src","sources":["constants/theme.ts"],"mappings":";;AAEA,OAAO,MAAMA,YAAmB,GAAG;EACjC,MAAM,EAAE;IACNC,UAAU,EAAE;EACd,CAAC;EACD,MAAM,EAAE;IACNC,KAAK,EAAE;EACT,CAAC;EACD,GAAG,EAAE;IACHA,KAAK,EAAE;EACT,CAAC;EACD,IAAI,EAAE;IACJA,KAAK,EAAE;EACT,CAAC;EACD,IAAI,EAAE;IACJA,KAAK,EAAE;EACT,CAAC;EACD,IAAI,EAAE;IACJA,KAAK,EAAE;EACT,CAAC;EACD,IAAI,EAAE;IACJA,KAAK,EAAE;EACT,CAAC;EACD,IAAI,EAAE;IACJA,KAAK,EAAE;EACT,CAAC;EACD,IAAI,EAAE;IACJA,KAAK,EAAE;EACT,CAAC;EACD,IAAI,EAAE;IACJA,KAAK,EAAE;EACT,CAAC;EACD,GAAG,EAAE;IACH,OAAO,EAAE,MAAM;IACf,gBAAgB,EAAE,MAAM;IACxB,QAAQ,EAAE;EACZ,CAAC;EACD,aAAa,EAAE;IACbD,UAAU,EAAE;EACd;AACF,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["defaultTheme","background","color","backgroundColor"],"sourceRoot":"../../../src","sources":["constants/theme.ts"],"mappings":";;AAEA,OAAO,MAAMA,YAAmB,GAAG;EACjC,MAAM,EAAE;IACNC,UAAU,EAAE;EACd,CAAC;EACD,MAAM,EAAE;IACNC,KAAK,EAAE,oBAAoB;IAC3BC,eAAe,EAAE;EACnB,CAAC;EACD,GAAG,EAAE;IACHD,KAAK,EAAE,oBAAoB;IAC3BC,eAAe,EAAE;EACnB,CAAC;EACD,IAAI,EAAE;IACJD,KAAK,EAAE,oBAAoB;IAC3BC,eAAe,EAAE;EACnB,CAAC;EACD,IAAI,EAAE;IACJD,KAAK,EAAE,oBAAoB;IAC3BC,eAAe,EAAE;EACnB,CAAC;EACD,IAAI,EAAE;IACJD,KAAK,EAAE,oBAAoB;IAC3BC,eAAe,EAAE;EACnB,CAAC;EACD,IAAI,EAAE;IACJD,KAAK,EAAE,oBAAoB;IAC3BC,eAAe,EAAE;EACnB,CAAC;EACD,IAAI,EAAE;IACJD,KAAK,EAAE,oBAAoB;IAC3BC,eAAe,EAAE;EACnB,CAAC;EACD,IAAI,EAAE;IACJD,KAAK,EAAE,oBAAoB;IAC3BC,eAAe,EAAE;EACnB,CAAC;EACD,IAAI,EAAE;IACJD,KAAK,EAAE,oBAAoB;IAC3BC,eAAe,EAAE;EACnB,CAAC;EACD,GAAG,EAAE;IACH,OAAO,EAAE,oBAAoB;IAC7B,iBAAiB,EAAE,SAAS;IAC5B,gBAAgB,EAAE,MAAM;IACxB,QAAQ,EAAE;EACZ,CAAC;EACD,GAAG,EAAE;IACHD,KAAK,EAAE,oBAAoB;IAC3BC,eAAe,EAAE;EACnB,CAAC;EACD,aAAa,EAAE;IACbF,UAAU,EAAE;EACd,CAAC;EACD,UAAU,EAAE;IACVC,KAAK,EAAE;EACT;AACF,CAAC","ignoreList":[]}
@@ -7,7 +7,11 @@ export const downloadEpub = async (url, fileName) => {
7
7
  if (file.exists) {
8
8
  return file.uri;
9
9
  }
10
- const downloadedFile = await File.downloadFileAsync(url, file);
10
+ const normalized = url.split('?X-Goog-Algorithm')[0];
11
+ if (!normalized) {
12
+ throw new Error('Invalid URL provided for EPUB download.');
13
+ }
14
+ const downloadedFile = await File.downloadFileAsync(normalized, file);
11
15
  return downloadedFile.uri;
12
16
  } catch (error) {
13
17
  console.error('Download Error:', error);
@@ -1 +1 @@
1
- {"version":3,"names":["File","Paths","downloadEpub","url","fileName","file","document","exists","uri","downloadedFile","downloadFileAsync","error","console"],"sourceRoot":"../../../src","sources":["helpers/downloadEpub.ts"],"mappings":";;AAAA,SAASA,IAAI,EAAEC,KAAK,QAAQ,kBAAkB;AAE9C,OAAO,MAAMC,YAAY,GAAG,MAAAA,CAC1BC,GAAW,EACXC,QAAgB,KACI;EACpB,IAAI;IACF,MAAMC,IAAI,GAAG,IAAIL,IAAI,CAACC,KAAK,CAACK,QAAQ,EAAEF,QAAQ,CAAC;IAE/C,IAAIC,IAAI,CAACE,MAAM,EAAE;MACf,OAAOF,IAAI,CAACG,GAAG;IACjB;IAEA,MAAMC,cAAc,GAAG,MAAMT,IAAI,CAACU,iBAAiB,CAACP,GAAG,EAAEE,IAAI,CAAC;IAE9D,OAAOI,cAAc,CAACD,GAAG;EAC3B,CAAC,CAAC,OAAOG,KAAK,EAAE;IACdC,OAAO,CAACD,KAAK,CAAC,iBAAiB,EAAEA,KAAK,CAAC;IACvC,MAAMA,KAAK;EACb;AACF,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["File","Paths","downloadEpub","url","fileName","file","document","exists","uri","normalized","split","Error","downloadedFile","downloadFileAsync","error","console"],"sourceRoot":"../../../src","sources":["helpers/downloadEpub.ts"],"mappings":";;AAAA,SAASA,IAAI,EAAEC,KAAK,QAAQ,kBAAkB;AAE9C,OAAO,MAAMC,YAAY,GAAG,MAAAA,CAC1BC,GAAW,EACXC,QAAgB,KACI;EACpB,IAAI;IACF,MAAMC,IAAI,GAAG,IAAIL,IAAI,CAACC,KAAK,CAACK,QAAQ,EAAEF,QAAQ,CAAC;IAE/C,IAAIC,IAAI,CAACE,MAAM,EAAE;MACf,OAAOF,IAAI,CAACG,GAAG;IACjB;IAEA,MAAMC,UAAU,GAAGN,GAAG,CAACO,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAEpD,IAAI,CAACD,UAAU,EAAE;MACf,MAAM,IAAIE,KAAK,CAAC,yCAAyC,CAAC;IAC5D;IAEA,MAAMC,cAAc,GAAG,MAAMZ,IAAI,CAACa,iBAAiB,CAACJ,UAAU,EAAEJ,IAAI,CAAC;IAErE,OAAOO,cAAc,CAACJ,GAAG;EAC3B,CAAC,CAAC,OAAOM,KAAK,EAAE;IACdC,OAAO,CAACD,KAAK,CAAC,iBAAiB,EAAEA,KAAK,CAAC;IACvC,MAAMA,KAAK;EACb;AACF,CAAC","ignoreList":[]}
@@ -18,4 +18,8 @@ export const checkTemplateFileExists = fileName => {
18
18
  const htmlFile = new File(Paths.document, fileName);
19
19
  return htmlFile.exists;
20
20
  };
21
+ export const getTemplateFileUri = fileName => {
22
+ const htmlFile = new File(Paths.document, fileName);
23
+ return htmlFile.uri;
24
+ };
21
25
  //# sourceMappingURL=saveTemplateToFile.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["File","Paths","saveTemplateToFile","template","fileName","htmlFile","document","exists","create","write","uri","error","console","checkTemplateFileExists"],"sourceRoot":"../../../src","sources":["helpers/saveTemplateToFile.ts"],"mappings":";;AAAA,SAASA,IAAI,EAAEC,KAAK,QAAQ,kBAAkB;AAE9C,OAAO,MAAMC,kBAAkB,GAAGA,CAACC,QAAgB,EAAEC,QAAgB,KAAK;EACxE,IAAI;IACF,MAAMC,QAAQ,GAAG,IAAIL,IAAI,CAACC,KAAK,CAACK,QAAQ,EAAEF,QAAQ,CAAC;IACnD,IAAI,CAACC,QAAQ,CAACE,MAAM,EAAE;MACpBF,QAAQ,CAACG,MAAM,CAAC,CAAC;IACnB;IACAH,QAAQ,CAACI,KAAK,CAACN,QAAQ,CAAC;IACxB,OAAOE,QAAQ,CAACK,GAAG;EACrB,CAAC,CAAC,OAAOC,KAAK,EAAE;IACdC,OAAO,CAACD,KAAK,CAAC,6BAA6B,EAAEA,KAAK,CAAC;IACnD,MAAMA,KAAK;EACb;AACF,CAAC;AAED,OAAO,MAAME,uBAAuB,GAAIT,QAAgB,IAAK;EAC3D,MAAMC,QAAQ,GAAG,IAAIL,IAAI,CAACC,KAAK,CAACK,QAAQ,EAAEF,QAAQ,CAAC;EACnD,OAAOC,QAAQ,CAACE,MAAM;AACxB,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["File","Paths","saveTemplateToFile","template","fileName","htmlFile","document","exists","create","write","uri","error","console","checkTemplateFileExists","getTemplateFileUri"],"sourceRoot":"../../../src","sources":["helpers/saveTemplateToFile.ts"],"mappings":";;AAAA,SAASA,IAAI,EAAEC,KAAK,QAAQ,kBAAkB;AAE9C,OAAO,MAAMC,kBAAkB,GAAGA,CAACC,QAAgB,EAAEC,QAAgB,KAAK;EACxE,IAAI;IACF,MAAMC,QAAQ,GAAG,IAAIL,IAAI,CAACC,KAAK,CAACK,QAAQ,EAAEF,QAAQ,CAAC;IACnD,IAAI,CAACC,QAAQ,CAACE,MAAM,EAAE;MACpBF,QAAQ,CAACG,MAAM,CAAC,CAAC;IACnB;IACAH,QAAQ,CAACI,KAAK,CAACN,QAAQ,CAAC;IACxB,OAAOE,QAAQ,CAACK,GAAG;EACrB,CAAC,CAAC,OAAOC,KAAK,EAAE;IACdC,OAAO,CAACD,KAAK,CAAC,6BAA6B,EAAEA,KAAK,CAAC;IACnD,MAAMA,KAAK;EACb;AACF,CAAC;AAED,OAAO,MAAME,uBAAuB,GAAIT,QAAgB,IAAK;EAC3D,MAAMC,QAAQ,GAAG,IAAIL,IAAI,CAACC,KAAK,CAACK,QAAQ,EAAEF,QAAQ,CAAC;EACnD,OAAOC,QAAQ,CAACE,MAAM;AACxB,CAAC;AAED,OAAO,MAAMO,kBAAkB,GAAIV,QAAgB,IAAK;EACtD,MAAMC,QAAQ,GAAG,IAAIL,IAAI,CAACC,KAAK,CAACK,QAAQ,EAAEF,QAAQ,CAAC;EACnD,OAAOC,QAAQ,CAACK,GAAG;AACrB,CAAC","ignoreList":[]}
@@ -9,9 +9,11 @@ export function useInjectWebViewVariables() {
9
9
  type,
10
10
  book,
11
11
  allowScriptedContent,
12
- theme
12
+ theme,
13
+ locations
13
14
  }) => {
14
- return template.replace(/<script id="jszip"><\/script>/, `<script src="${jszip}"></script>`).replace(/<script id="epubjs"><\/script>/, `<script src="${epubjs}"></script>`).replace(/const type = window.type;/, `const type = '${type}';`).replace(/const file = window.book;/, `const file = '${book}';`).replace(/const enableSelection = window.enable_selection;/, `const enableSelection = false;`).replace(/allowScriptedContent: allowScriptedContent/, `allowScriptedContent: ${allowScriptedContent}`).replace(/allowPopups: allowPopups/, `allowPopups: false`).replace(/const theme = window.theme;/, `const theme = ${JSON.stringify(theme)};`);
15
+ const initialLocations = locations && locations.length > 0 ? JSON.stringify(locations) : 'null';
16
+ return template.replace(/<script id="jszip"><\/script>/, `<script src="${jszip}"></script>`).replace(/<script id="epubjs"><\/script>/, `<script src="${epubjs}"></script>`).replace(/const type = window.type;/, `const type = '${type}';`).replace(/const file = window.book;/, `const file = '${book}';`).replace(/const enableSelection = window.enable_selection;/, `const enableSelection = false;`).replace(/allowScriptedContent: allowScriptedContent/, `allowScriptedContent: ${allowScriptedContent}`).replace(/allowPopups: allowPopups/, `allowPopups: false`).replace(/const theme = window.theme;/, `const theme = ${JSON.stringify(theme)};`).replace(/const initialLocations = window.locations;/, `const initialLocations = ${initialLocations};`);
15
17
  }, []);
16
18
  return {
17
19
  injectWebViewVariables
@@ -1 +1 @@
1
- {"version":3,"names":["useCallback","template","useInjectWebViewVariables","injectWebViewVariables","jszip","epubjs","type","book","allowScriptedContent","theme","replace","JSON","stringify"],"sourceRoot":"../../../src","sources":["hooks/useInjectWebviewVariables.ts"],"mappings":";;AAAA,SAASA,WAAW,QAAQ,OAAO;AAGnC,OAAOC,QAAQ,MAAM,0BAAuB;AAE5C,OAAO,SAASC,yBAAyBA,CAAA,EAAG;EAC1C,MAAMC,sBAAsB,GAAGH,WAAW,CACxC,CAAC;IACCI,KAAK;IACLC,MAAM;IACNC,IAAI;IACJC,IAAI;IACJC,oBAAoB;IACpBC;EAQF,CAAC,KAAK;IACJ,OAAOR,QAAQ,CACZS,OAAO,CACN,+BAA+B,EAC/B,gBAAgBN,KAAK,aACvB,CAAC,CACAM,OAAO,CACN,gCAAgC,EAChC,gBAAgBL,MAAM,aACxB,CAAC,CACAK,OAAO,CAAC,2BAA2B,EAAE,iBAAiBJ,IAAI,IAAI,CAAC,CAC/DI,OAAO,CAAC,2BAA2B,EAAE,iBAAiBH,IAAI,IAAI,CAAC,CAC/DG,OAAO,CACN,kDAAkD,EAClD,gCACF,CAAC,CACAA,OAAO,CACN,4CAA4C,EAC5C,yBAAyBF,oBAAoB,EAC/C,CAAC,CACAE,OAAO,CAAC,0BAA0B,EAAE,oBAAoB,CAAC,CACzDA,OAAO,CACN,6BAA6B,EAC7B,iBAAiBC,IAAI,CAACC,SAAS,CAACH,KAAK,CAAC,GACxC,CAAC;EACL,CAAC,EACD,EACF,CAAC;EACD,OAAO;IAAEN;EAAuB,CAAC;AACnC","ignoreList":[]}
1
+ {"version":3,"names":["useCallback","template","useInjectWebViewVariables","injectWebViewVariables","jszip","epubjs","type","book","allowScriptedContent","theme","locations","initialLocations","length","JSON","stringify","replace"],"sourceRoot":"../../../src","sources":["hooks/useInjectWebviewVariables.ts"],"mappings":";;AAAA,SAASA,WAAW,QAAQ,OAAO;AAGnC,OAAOC,QAAQ,MAAM,0BAAuB;AAE5C,OAAO,SAASC,yBAAyBA,CAAA,EAAG;EAC1C,MAAMC,sBAAsB,GAAGH,WAAW,CACxC,CAAC;IACCI,KAAK;IACLC,MAAM;IACNC,IAAI;IACJC,IAAI;IACJC,oBAAoB;IACpBC,KAAK;IACLC;EASF,CAAC,KAAK;IACJ,MAAMC,gBAAgB,GACpBD,SAAS,IAAIA,SAAS,CAACE,MAAM,GAAG,CAAC,GAAGC,IAAI,CAACC,SAAS,CAACJ,SAAS,CAAC,GAAG,MAAM;IAExE,OAAOT,QAAQ,CACZc,OAAO,CACN,+BAA+B,EAC/B,gBAAgBX,KAAK,aACvB,CAAC,CACAW,OAAO,CACN,gCAAgC,EAChC,gBAAgBV,MAAM,aACxB,CAAC,CACAU,OAAO,CAAC,2BAA2B,EAAE,iBAAiBT,IAAI,IAAI,CAAC,CAC/DS,OAAO,CAAC,2BAA2B,EAAE,iBAAiBR,IAAI,IAAI,CAAC,CAC/DQ,OAAO,CACN,kDAAkD,EAClD,gCACF,CAAC,CACAA,OAAO,CACN,4CAA4C,EAC5C,yBAAyBP,oBAAoB,EAC/C,CAAC,CACAO,OAAO,CAAC,0BAA0B,EAAE,oBAAoB,CAAC,CACzDA,OAAO,CACN,6BAA6B,EAC7B,iBAAiBF,IAAI,CAACC,SAAS,CAACL,KAAK,CAAC,GACxC,CAAC,CACAM,OAAO,CACN,4CAA4C,EAC5C,4BAA4BJ,gBAAgB,GAC9C,CAAC;EACL,CAAC,EACD,EACF,CAAC;EACD,OAAO;IAAER;EAAuB,CAAC;AACnC","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/GestureHandler/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAQnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,QAAA,MAAM,cAAc,EAAE,EAAE,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,CA4C9D,CAAC;AAQF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/GestureHandler/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAQnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,QAAA,MAAM,cAAc,EAAE,EAAE,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,CAoD9D,CAAC;AAQF,eAAe,cAAc,CAAC"}
@@ -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,4CA8Ob,CAAC;AAaF,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=')\n 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=')\n 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 =\n window.ReactNativeWebView !== undefined &&\n window.ReactNativeWebView !== null\n ? window.ReactNativeWebView\n : window;\n reactNativeWebview.postMessage(\n JSON.stringify({\n type: 'onDisplayError',\n reason: 'Book file is missing',\n })\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 =\n window.ReactNativeWebView !== undefined &&\n window.ReactNativeWebView !== null\n ? window.ReactNativeWebView\n : window;\n reactNativeWebview.postMessage(\n JSON.stringify({\n type: 'onDisplayError',\n reason: 'Missing or invalid file type',\n })\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 =\n window.ReactNativeWebView !== undefined &&\n window.ReactNativeWebView !== null\n ? window.ReactNativeWebView\n : window;\n reactNativeWebview.postMessage(JSON.stringify({ type: 'onStarted' }));\n\n function flatten(chapters) {\n return [].concat.apply(\n [],\n chapters.map((chapter) =>\n [].concat.apply([chapter], flatten(chapter.subitems))\n )\n );\n }\n\n function getCfiFromHref(book, href) {\n const [_, id] = href.split('#');\n let section =\n book.spine.get(href.split('/')[1]) ||\n book.spine.get(href) ||\n book.spine.get(href.split('/').slice(1).join('/'));\n\n const el = id\n ? section.document.getElementById(id)\n : 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 =\n ePub.CFI.prototype.compare(\n location.start.cfi,\n getCfiFromHref(book, chapter.href)\n ) > 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),\n 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 (\n 'epubcfi(' +\n CFI.segmentString(cfi.base) +\n '!' +\n CFI.segmentString(cfi.path) +\n ',' +\n CFI.segmentString(cfi.start) +\n ',' +\n CFI.segmentString(cfi.end) +\n ')'\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(\n 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\n if (initialLocations) {\n reactNativeWebview.postMessage(\n 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 );\n return Promise.resolve();\n }\n\n return book.locations\n .generate(1600)\n .then(function () {\n var generatedLocation =\n rendition.currentLocation() || currentLocation;\n reactNativeWebview.postMessage(\n 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(\n generatedLocation.start.cfi\n )\n : 0,\n })\n );\n })\n .catch(function () {\n reactNativeWebview.postMessage(\n 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 = (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(\n JSON.stringify({\n type: 'onNavigationLoaded',\n toc: item.toc,\n landmarks: item.landmarks,\n })\n );\n });\n })\n .catch(function (err) {\n reactNativeWebview.postMessage(\n JSON.stringify({\n type: 'onDisplayError',\n reason: err.message || err.toString(),\n })\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(\n JSON.stringify({\n type: 'onLocationChange',\n totalLocations: book.locations.total,\n currentLocation: location,\n progress: percentage,\n currentSection: chapter,\n })\n );\n\n if (location.atStart) {\n reactNativeWebview.postMessage(\n JSON.stringify({\n type: 'onBeginning',\n })\n );\n }\n\n if (location.atEnd) {\n reactNativeWebview.postMessage(\n JSON.stringify({\n type: 'onFinish',\n })\n );\n }\n });\n\n rendition.on('rendered', function (section) {\n reactNativeWebview.postMessage(\n JSON.stringify({\n type: 'onRendered',\n section: section,\n currentSection: book.navigation.get(section.href),\n })\n );\n });\n\n rendition.on('layout', function (layout) {\n reactNativeWebview.postMessage(\n JSON.stringify({\n type: 'onLayout',\n layout: layout,\n })\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,wBAubE"}
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,CAehB,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.2",
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);
146
+
147
+ if (
148
+ typeof beginAt === 'number' &&
149
+ !initialLocation &&
150
+ !hasAppliedBeginAtRef.current
151
+ ) {
152
+ goToProgress(beginAt);
153
+ hasAppliedBeginAtRef.current = true;
154
+ }
108
155
 
109
- return onLocationsReady(props.epubKey, parsedEvent.locations);
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,27 +232,46 @@ 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
- const htmlTemplateName = useMemo(
170
- () => src.split('/').pop()?.replace('.epub', '.html') || 'index.html',
171
- [src]
172
- );
258
+ const epubFileName = useMemo(() => {
259
+ const splited = src.split('/').pop() || 'book.epub';
260
+ const cleanName = splited.split('?')[0] || 'book.epub';
261
+ const decoded = decodeURIComponent(cleanName)
262
+ .replace(' ', '_')
263
+ .replace(',', '_')
264
+ .replace(/[^a-zA-Z0-9._-]/g, '');
173
265
 
174
- const epubFileName = useMemo(
175
- () => src.split('/').pop() || 'book.epub',
176
- [src]
266
+ return decoded;
267
+ }, [src]);
268
+
269
+ const htmlTemplateName = useMemo(
270
+ () =>
271
+ epubFileName
272
+ .replace('.epub', '-template.html')
273
+ .replace('.zip', '-template.html'),
274
+ [epubFileName]
177
275
  );
178
276
 
179
277
  const handleBookRef = useCallback(
@@ -192,8 +290,10 @@ const Reader = ({
192
290
  const prepareReader = async () => {
193
291
  try {
194
292
  setIsLoading(true);
195
- setIsReaderReady(false);
196
293
  setTemplateUri('');
294
+ hasAppliedBeginAtRef.current = false;
295
+ hasPersistedLocationsRef.current = false;
296
+ hasLocationsReadyRef.current = false;
197
297
 
198
298
  const [, jszip, epubjs] = await loadScripts();
199
299
 
@@ -201,6 +301,15 @@ const Reader = ({
201
301
 
202
302
  const localEpubUri = await downloadEpub(src, epubFileName);
203
303
 
304
+ templateAssetsRef.current = { jszip, epubjs, localEpubUri };
305
+
306
+ if (checkTemplateFileExists(htmlTemplateName)) {
307
+ if (isMounted) {
308
+ setTemplateUri(getTemplateFileUri(htmlTemplateName));
309
+ }
310
+ return;
311
+ }
312
+
204
313
  const generatedTemplate = injectWebViewVariables({
205
314
  jszip,
206
315
  epubjs,
@@ -235,7 +344,7 @@ const Reader = ({
235
344
  setIsLoading,
236
345
  ]);
237
346
 
238
- if (isLoading || !templateUri) {
347
+ if (!templateUri) {
239
348
  return LoaderComponent ? (
240
349
  <LoaderComponent />
241
350
  ) : (
@@ -247,12 +356,14 @@ const Reader = ({
247
356
  }
248
357
 
249
358
  return (
250
- <>
359
+ <View style={styles.container}>
251
360
  <GestureHandler
252
361
  onSwipeLeft={handleOnSwipeLeft}
253
362
  onSwipeRight={handleOnSwipeRight}
254
363
  onTap={handleOnTap}
364
+ onPinchStart={handleOnPinchStart}
255
365
  onPinch={handleOnPinch}
366
+ onPinchEnd={handleOnPinchEnd}
256
367
  >
257
368
  <WebView
258
369
  pointerEvents="none"
@@ -269,10 +380,27 @@ const Reader = ({
269
380
  allowFileAccess
270
381
  javaScriptCanOpenWindowsAutomatically
271
382
  onMessage={onMessage}
383
+ onLoadEnd={() => {
384
+ if (!waitForLocationsReady || hasLocationsReadyRef.current) {
385
+ setIsLoading(false);
386
+ }
387
+ }}
272
388
  style={styles.container}
273
389
  />
274
390
  </GestureHandler>
275
- </>
391
+
392
+ {isLoading &&
393
+ (LoaderComponent ? (
394
+ <View style={styles.loaderOverlay}>
395
+ <LoaderComponent />
396
+ </View>
397
+ ) : (
398
+ <View style={styles.loaderOverlay}>
399
+ <ActivityIndicator size="large" />
400
+ <Text>Przygotowuję książkę...</Text>
401
+ </View>
402
+ ))}
403
+ </View>
276
404
  );
277
405
  };
278
406
 
@@ -285,6 +413,12 @@ const styles = StyleSheet.create({
285
413
  justifyContent: 'center',
286
414
  alignItems: 'center',
287
415
  },
416
+ loaderOverlay: {
417
+ ...StyleSheet.absoluteFillObject,
418
+ justifyContent: 'center',
419
+ alignItems: 'center',
420
+ backgroundColor: '#ffffff',
421
+ },
288
422
  });
289
423
 
290
424
  export default Reader;