@epubjs-react-native/core
Version:
A digital book reader in .opf .epub format for react native using epub.js library inside a webview.
776 lines (761 loc) • 24.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ReaderContext = void 0;
exports.ReaderProvider = ReaderProvider;
exports.defaultTheme = void 0;
var _react = _interopRequireWildcard(require("react"));
var webViewInjectFunctions = _interopRequireWildcard(require("./utils/webViewInjectFunctions"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
var Types = /*#__PURE__*/function (Types) {
Types["CHANGE_THEME"] = "CHANGE_THEME";
Types["CHANGE_FONT_SIZE"] = "CHANGE_FONT_SIZE";
Types["CHANGE_FONT_FAMILY"] = "CHANGE_FONT_FAMILY";
Types["SET_AT_START"] = "SET_AT_START";
Types["SET_AT_END"] = "SET_AT_END";
Types["SET_KEY"] = "SET_KEY";
Types["SET_TOTAL_LOCATIONS"] = "SET_TOTAL_LOCATIONS";
Types["SET_CURRENT_LOCATION"] = "SET_CURRENT_LOCATION";
Types["SET_META"] = "SET_META";
Types["SET_PROGRESS"] = "SET_PROGRESS";
Types["SET_LOCATIONS"] = "SET_LOCATIONS";
Types["SET_IS_LOADING"] = "SET_IS_LOADING";
Types["SET_IS_RENDERING"] = "SET_IS_RENDERING";
Types["SET_SEARCH_RESULTS"] = "SET_SEARCH_RESULTS";
Types["SET_IS_SEARCHING"] = "SET_IS_SEARCHING";
Types["SET_ANNOTATIONS"] = "SET_ANNOTATIONS";
Types["SET_SECTION"] = "SET_SECTION";
Types["SET_TOC"] = "SET_TOC";
Types["SET_LANDMARKS"] = "SET_LANDMARKS";
Types["SET_BOOKMARKS"] = "SET_BOOKMARKS";
Types["SET_IS_BOOKMARKED"] = "SET_IS_BOOKMARKED";
Types["SET_FLOW"] = "SET_FLOW";
return Types;
}(Types || {});
const defaultTheme = exports.defaultTheme = {
'body': {
background: '#fff'
},
'span': {
color: '#000 !important'
},
'p': {
color: '#000 !important'
},
'li': {
color: '#000 !important'
},
'h1': {
color: '#000 !important'
},
'a': {
'color': '#000 !important',
'pointer-events': 'auto',
'cursor': 'pointer'
},
'::selection': {
background: 'lightskyblue'
}
};
const initialState = {
theme: defaultTheme,
fontFamily: 'Helvetica',
fontSize: '12pt',
atStart: false,
atEnd: false,
key: '',
totalLocations: 0,
currentLocation: null,
meta: {
cover: '',
author: '',
title: '',
description: '',
language: '',
publisher: '',
rights: ''
},
progress: 0,
locations: [],
isLoading: true,
isRendering: true,
isSearching: false,
searchResults: {
results: [],
totalResults: 0
},
annotations: [],
section: null,
toc: [],
landmarks: [],
bookmarks: [],
isBookmarked: false,
flow: 'auto'
};
function bookReducer(state, action) {
switch (action.type) {
case Types.CHANGE_THEME:
return {
...state,
theme: action.payload
};
case Types.CHANGE_FONT_SIZE:
return {
...state,
fontSize: action.payload
};
case Types.CHANGE_FONT_FAMILY:
return {
...state,
fontFamily: action.payload
};
case Types.SET_AT_START:
return {
...state,
atStart: action.payload
};
case Types.SET_AT_END:
return {
...state,
atEnd: action.payload
};
case Types.SET_KEY:
return {
...state,
key: action.payload
};
case Types.SET_TOTAL_LOCATIONS:
return {
...state,
totalLocations: action.payload
};
case Types.SET_CURRENT_LOCATION:
return {
...state,
currentLocation: action.payload
};
case Types.SET_META:
return {
...state,
meta: action.payload
};
case Types.SET_PROGRESS:
return {
...state,
progress: action.payload
};
case Types.SET_LOCATIONS:
return {
...state,
locations: action.payload
};
case Types.SET_IS_LOADING:
return {
...state,
isLoading: action.payload
};
case Types.SET_IS_RENDERING:
return {
...state,
isRendering: action.payload
};
case Types.SET_IS_SEARCHING:
return {
...state,
isSearching: action.payload
};
case Types.SET_SEARCH_RESULTS:
return {
...state,
searchResults: action.payload
};
case Types.SET_ANNOTATIONS:
return {
...state,
annotations: action.payload
};
case Types.SET_SECTION:
return {
...state,
section: action.payload
};
case Types.SET_TOC:
return {
...state,
toc: action.payload
};
case Types.SET_LANDMARKS:
return {
...state,
landmarks: action.payload
};
case Types.SET_BOOKMARKS:
return {
...state,
bookmarks: action.payload
};
case Types.SET_IS_BOOKMARKED:
return {
...state,
isBookmarked: action.payload
};
case Types.SET_FLOW:
return {
...state,
flow: action.payload
};
default:
return state;
}
}
const ReaderContext = exports.ReaderContext = /*#__PURE__*/(0, _react.createContext)({
registerBook: () => {},
setAtStart: () => {},
setAtEnd: () => {},
setTotalLocations: () => {},
setCurrentLocation: () => {},
setMeta: () => {},
setProgress: () => {},
setLocations: () => {},
setIsLoading: () => {},
setIsRendering: () => {},
goToLocation: () => {},
goPrevious: () => {},
goNext: () => {},
getLocations: () => [],
getCurrentLocation: () => null,
getMeta: () => ({
cover: '',
author: '',
title: '',
description: '',
language: '',
publisher: '',
rights: ''
}),
search: () => {},
clearSearchResults: () => {},
setIsSearching: () => {},
changeTheme: () => {},
changeFontFamily: () => {},
changeFontSize: () => {},
setKey: () => {},
setSection: () => {},
setToc: () => {},
setLandmarks: () => {},
addBookmark: () => {},
removeBookmark: () => {},
removeBookmarks: () => {},
updateBookmark: () => {},
setBookmarks: () => {},
setIsBookmarked: () => {},
key: '',
theme: defaultTheme,
atStart: false,
atEnd: false,
totalLocations: 0,
currentLocation: null,
meta: {
cover: '',
author: '',
title: '',
description: '',
language: '',
publisher: '',
rights: ''
},
progress: 0,
locations: [],
isLoading: true,
isRendering: true,
isSearching: false,
searchResults: {
results: [],
totalResults: 0
},
setSearchResults: () => {},
removeSelection: () => {},
addAnnotation: () => {},
addAnnotationByTagId: () => {},
updateAnnotation: () => {},
updateAnnotationByTagId: () => {},
removeAnnotation: () => {},
removeAnnotationByTagId: () => {},
removeAnnotationByCfi: () => {},
removeAnnotations: () => {},
setAnnotations: () => {},
setInitialAnnotations: () => {},
annotations: [],
section: null,
toc: [],
landmarks: [],
bookmarks: [],
isBookmarked: false,
injectJavascript: () => {},
changeFlow: () => {},
setFlow: () => {},
flow: 'auto'
});
function ReaderProvider({
children
}) {
const [state, dispatch] = (0, _react.useReducer)(bookReducer, initialState);
const book = (0, _react.useRef)(null);
const registerBook = (0, _react.useCallback)(bookRef => {
book.current = bookRef;
}, []);
const changeTheme = (0, _react.useCallback)(theme => {
book.current?.injectJavaScript(`
rendition.themes.register({ theme: ${JSON.stringify(theme)} });
rendition.themes.select('theme');
rendition.views().forEach(view => view.pane ? view.pane.render() : null); true;
`);
dispatch({
type: Types.CHANGE_THEME,
payload: theme
});
}, []);
const changeFontFamily = (0, _react.useCallback)(fontFamily => {
book.current?.injectJavaScript(`
rendition.themes.font('${fontFamily}');
rendition.views().forEach(view => view.pane ? view.pane.render() : null); true;
`);
dispatch({
type: Types.CHANGE_FONT_FAMILY,
payload: fontFamily
});
}, []);
const changeFontSize = (0, _react.useCallback)(size => {
book.current?.injectJavaScript(`
rendition.themes.fontSize('${size}');
rendition.views().forEach(view => view.pane ? view.pane.render() : null); true;
`);
dispatch({
type: Types.CHANGE_FONT_SIZE,
payload: size
});
}, []);
const setAtStart = (0, _react.useCallback)(atStart => {
dispatch({
type: Types.SET_AT_START,
payload: atStart
});
}, []);
const setAtEnd = (0, _react.useCallback)(atEnd => {
dispatch({
type: Types.SET_AT_END,
payload: atEnd
});
}, []);
const setTotalLocations = (0, _react.useCallback)(totalLocations => {
dispatch({
type: Types.SET_TOTAL_LOCATIONS,
payload: totalLocations
});
}, []);
const setCurrentLocation = (0, _react.useCallback)(location => {
dispatch({
type: Types.SET_CURRENT_LOCATION,
payload: location
});
}, []);
const setMeta = (0, _react.useCallback)(meta => {
dispatch({
type: Types.SET_META,
payload: meta
});
}, []);
const setProgress = (0, _react.useCallback)(progress => {
dispatch({
type: Types.SET_PROGRESS,
payload: progress
});
}, []);
const setLocations = (0, _react.useCallback)(locations => {
dispatch({
type: Types.SET_LOCATIONS,
payload: locations
});
}, []);
const setIsLoading = (0, _react.useCallback)(isLoading => {
dispatch({
type: Types.SET_IS_LOADING,
payload: isLoading
});
}, []);
const setIsRendering = (0, _react.useCallback)(isRendering => {
dispatch({
type: Types.SET_IS_RENDERING,
payload: isRendering
});
}, []);
const goToLocation = (0, _react.useCallback)(targetCfi => {
book.current?.injectJavaScript(`rendition.display('${targetCfi}'); true`);
}, []);
const goPrevious = (0, _react.useCallback)(options => {
webViewInjectFunctions.injectJavaScript(book, `
${!options?.keepScrollOffset && state.flow === 'scrolled-doc' ? `rendition.once('relocated', () => rendition.moveTo(0));` : ''}
rendition.prev();
`);
}, [state.flow]);
const goNext = (0, _react.useCallback)(options => {
webViewInjectFunctions.injectJavaScript(book, `
${!options?.keepScrollOffset && state.flow === 'scrolled-doc' ? `rendition.once('relocated', () => rendition.moveTo(0));` : ''}
rendition.next();
`);
}, [state.flow]);
const getLocations = (0, _react.useCallback)(() => state.locations, [state.locations]);
const getCurrentLocation = (0, _react.useCallback)(() => state.currentLocation, [state.currentLocation]);
const getMeta = (0, _react.useCallback)(() => state.meta, [state.meta]);
const search = (0, _react.useCallback)((term, page, limit, options) => {
dispatch({
type: Types.SET_SEARCH_RESULTS,
payload: {
results: [],
totalResults: 0
}
});
dispatch({
type: Types.SET_IS_SEARCHING,
payload: true
});
webViewInjectFunctions.injectJavaScript(book, `
const page = ${page || 1};
const limit = ${limit || 20};
const term = ${JSON.stringify(term)};
const chapterId = ${JSON.stringify(options?.sectionId)};
const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView!== null ? window.ReactNativeWebView: window;
if (!term) {
reactNativeWebview.postMessage(
JSON.stringify({ type: 'onSearch', results: [] })
);
} else {
Promise.all(
book.spine.spineItems.map((item) => {
return item.load(book.load.bind(book)).then(() => {
let results = item.find(term.trim());
const locationHref = item.href;
let [match] = flatten(book.navigation.toc)
.filter((chapter, index) => {
return book.canonical(chapter.href).includes(locationHref)
}, null);
if (results.length > 0) {
results = results.map(result => ({ ...result, section: { ...match, index: book.navigation.toc.findIndex(elem => elem.id === match?.id) } }));
if (chapterId) {
results = results.filter(result => result.section.id === chapterId);
}
}
item.unload();
return Promise.resolve(results);
});
})
).then((results) => {
const items = [].concat.apply([], results);
reactNativeWebview.postMessage(
JSON.stringify({ type: 'onSearch', results: items.slice((page - 1) * limit, page * limit), totalResults: items.length })
);
}).catch(err => {
alert(err?.message);
reactNativeWebview.postMessage(
JSON.stringify({ type: 'onSearch', results: [], totalResults: 0 })
);
})
}
`);
}, []);
const clearSearchResults = (0, _react.useCallback)(() => {
dispatch({
type: Types.SET_SEARCH_RESULTS,
payload: {
results: [],
totalResults: 0
}
});
}, []);
const setIsSearching = (0, _react.useCallback)(value => {
dispatch({
type: Types.SET_IS_SEARCHING,
payload: value
});
}, []);
const setSearchResults = (0, _react.useCallback)(({
results,
totalResults
}) => {
dispatch({
type: Types.SET_SEARCH_RESULTS,
payload: {
results,
totalResults
}
});
}, []);
const addAnnotation = (0, _react.useCallback)((type, cfiRange, data, styles, iconClass = '') => {
webViewInjectFunctions.injectJavaScript(book, `
${webViewInjectFunctions.addAnnotation(type, cfiRange, data, iconClass, styles)}
${webViewInjectFunctions.onChangeAnnotations()}
`);
}, []);
const addAnnotationByTagId = (0, _react.useCallback)((type, tagId, data, styles, iconClass = '') => {
webViewInjectFunctions.injectJavaScript(book, webViewInjectFunctions.addAnnotationByTagId(type, tagId, data, iconClass, styles));
}, []);
const updateAnnotation = (0, _react.useCallback)((annotation, data = {}, styles) => {
webViewInjectFunctions.injectJavaScript(book, webViewInjectFunctions.updateAnnotation(annotation, data, styles));
}, []);
const updateAnnotationByTagId = (0, _react.useCallback)((tagId, data = {}, styles) => {
webViewInjectFunctions.injectJavaScript(book, webViewInjectFunctions.updateAnnotationByTagId(tagId, data, styles));
}, []);
const removeAnnotation = (0, _react.useCallback)(annotation => {
webViewInjectFunctions.injectJavaScript(book, `
rendition.annotations.remove(${JSON.stringify(annotation.cfiRange)}, ${JSON.stringify(annotation.type)});
${webViewInjectFunctions.onChangeAnnotations()}
`);
}, []);
const removeAnnotationByTagId = (0, _react.useCallback)(tagId => {
webViewInjectFunctions.injectJavaScript(book, webViewInjectFunctions.removeAnnotationByTagId(tagId));
}, []);
const removeAnnotationByCfi = (0, _react.useCallback)(cfiRange => {
webViewInjectFunctions.injectJavaScript(book, `
['highlight', 'underline', 'mark'].forEach(type => {
rendition.annotations.remove('${cfiRange}', type);
});
${webViewInjectFunctions.onChangeAnnotations()}
`);
}, []);
const removeAnnotations = (0, _react.useCallback)(type => {
webViewInjectFunctions.injectJavaScript(book, `
let annotations = Object.values(rendition.annotations._annotations);
if (typeof ${type} === 'string') {
annotations = annotations.filter(annotation => annotation.type === ${type});
}
annotations.forEach(annotation => {
rendition.annotations.remove(annotation.cfiRange, annotation.type);
});
${webViewInjectFunctions.onChangeAnnotations()}
`);
}, []);
const setAnnotations = (0, _react.useCallback)(annotations => {
dispatch({
type: Types.SET_ANNOTATIONS,
payload: annotations
});
}, []);
const setInitialAnnotations = (0, _react.useCallback)(annotations => {
annotations.forEach(annotation => {
webViewInjectFunctions.injectJavaScript(book, webViewInjectFunctions.addAnnotation(annotation.type, annotation.cfiRange, annotation.data, annotation.iconClass, annotation.styles, annotation.cfiRangeText, true));
});
const transform = JSON.stringify(annotations);
webViewInjectFunctions.injectJavaScript(book, `
const initialAnnotations = JSON.stringify(${transform});
const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView!== null ? window.ReactNativeWebView: window;
reactNativeWebview.postMessage(JSON.stringify({
type: 'onSetInitialAnnotations',
annotations: ${webViewInjectFunctions.mapArrayObjectsToAnnotations('JSON.parse(initialAnnotations)')}
}));
`);
}, []);
const setKey = (0, _react.useCallback)(key => {
dispatch({
type: Types.SET_KEY,
payload: key
});
}, []);
const removeSelection = (0, _react.useCallback)(() => {
webViewInjectFunctions.injectJavaScript(book, `
const getSelections = () => rendition.getContents().map(contents => contents.window.getSelection());
const clearSelection = () => getSelections().forEach(s => s.removeAllRanges());
clearSelection();
`);
}, []);
const setSection = (0, _react.useCallback)(section => {
dispatch({
type: Types.SET_SECTION,
payload: section
});
}, []);
const setToc = (0, _react.useCallback)(toc => {
dispatch({
type: Types.SET_TOC,
payload: toc
});
}, []);
const setLandmarks = (0, _react.useCallback)(landmarks => {
dispatch({
type: Types.SET_LANDMARKS,
payload: landmarks
});
}, []);
const addBookmark = (0, _react.useCallback)((location, data) => {
webViewInjectFunctions.injectJavaScript(book, `
const location = ${JSON.stringify(location)};
const chapter = getChapter(${JSON.stringify(location)});
const cfi = makeRangeCfi(location.start.cfi, location.end.cfi);
const data = ${JSON.stringify(data)};
const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView!== null ? window.ReactNativeWebView: window;
book.getRange(cfi).then(range => {
reactNativeWebview.postMessage(JSON.stringify({
type: "onAddBookmark",
bookmark: {
id: Date.now(),
chapter,
location,
text: range.toString(),
data,
},
}));
}).catch(error => alert(error?.message));
`);
}, []);
const removeBookmark = (0, _react.useCallback)(bookmark => {
webViewInjectFunctions.injectJavaScript(book, `
const bookmark = ${JSON.stringify(bookmark)};
const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView!== null ? window.ReactNativeWebView: window;
reactNativeWebview.postMessage(JSON.stringify({
type: "onRemoveBookmark",
bookmark,
}));
`);
dispatch({
type: Types.SET_BOOKMARKS,
payload: state.bookmarks.filter(({
id
}) => id !== bookmark.id)
});
}, [state.bookmarks]);
const removeBookmarks = (0, _react.useCallback)(() => {
dispatch({
type: Types.SET_BOOKMARKS,
payload: []
});
webViewInjectFunctions.injectJavaScript(book, `
const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView!== null ? window.ReactNativeWebView: window;
reactNativeWebview.postMessage(JSON.stringify({
type: "onRemoveBookmarks",
}));
`);
}, []);
const setBookmarks = (0, _react.useCallback)(bookmarks => {
dispatch({
type: Types.SET_BOOKMARKS,
payload: bookmarks
});
}, []);
const updateBookmark = (0, _react.useCallback)((id, data) => {
const {
bookmarks
} = state;
const bookmark = state.bookmarks.find(item => item.id === id);
if (!bookmark) return;
bookmark.data = data;
const index = state.bookmarks.findIndex(item => item.id === id);
bookmarks[index] = bookmark;
dispatch({
type: Types.SET_BOOKMARKS,
payload: bookmarks
});
webViewInjectFunctions.injectJavaScript(book, `
const bookmark = ${JSON.stringify(bookmark)};
const reactNativeWebview = window.ReactNativeWebView !== undefined && window.ReactNativeWebView!== null ? window.ReactNativeWebView: window;
reactNativeWebview.postMessage(JSON.stringify({
type: "onUpdateBookmark",
bookmark,
}));
`);
}, [state]);
const setIsBookmarked = (0, _react.useCallback)(value => {
dispatch({
type: Types.SET_IS_BOOKMARKED,
payload: value
});
}, []);
const injectJavascript = (0, _react.useCallback)(script => {
book.current?.injectJavaScript(script);
}, []);
const changeFlow = (0, _react.useCallback)(flow => {
webViewInjectFunctions.injectJavaScript(book, `rendition.flow(${JSON.stringify(flow)}); true`);
dispatch({
type: Types.SET_FLOW,
payload: flow
});
}, []);
const setFlow = (0, _react.useCallback)(flow => {
dispatch({
type: Types.SET_FLOW,
payload: flow
});
}, []);
const contextValue = (0, _react.useMemo)(() => ({
registerBook,
setAtStart,
setAtEnd,
setTotalLocations,
setCurrentLocation,
setMeta,
setProgress,
setLocations,
setIsLoading,
setIsRendering,
goToLocation,
goPrevious,
goNext,
getLocations,
getCurrentLocation,
getMeta,
search,
clearSearchResults,
setIsSearching,
setKey,
key: state.key,
changeTheme,
changeFontFamily,
changeFontSize,
theme: state.theme,
atStart: state.atStart,
atEnd: state.atEnd,
totalLocations: state.totalLocations,
currentLocation: state.currentLocation,
meta: state.meta,
progress: state.progress,
locations: state.locations,
isLoading: state.isLoading,
isRendering: state.isRendering,
isSearching: state.isSearching,
searchResults: state.searchResults,
setSearchResults,
removeSelection,
addAnnotation,
addAnnotationByTagId,
updateAnnotation,
updateAnnotationByTagId,
removeAnnotation,
removeAnnotationByTagId,
removeAnnotationByCfi,
removeAnnotations,
setAnnotations,
setInitialAnnotations,
annotations: state.annotations,
setSection,
setToc,
setLandmarks,
section: state.section,
toc: state.toc,
landmarks: state.landmarks,
addBookmark,
removeBookmark,
removeBookmarks,
updateBookmark,
setBookmarks,
bookmarks: state.bookmarks,
setIsBookmarked,
isBookmarked: state.isBookmarked,
injectJavascript,
changeFlow,
setFlow,
flow: state.flow
}), [changeFontFamily, changeFontSize, changeTheme, getCurrentLocation, getMeta, getLocations, goNext, goPrevious, goToLocation, registerBook, search, clearSearchResults, setIsSearching, setAtEnd, setAtStart, setCurrentLocation, setMeta, setIsLoading, setIsRendering, setKey, setLocations, setProgress, setSearchResults, setTotalLocations, removeSelection, addAnnotation, addAnnotationByTagId, updateAnnotation, updateAnnotationByTagId, removeAnnotation, removeAnnotationByTagId, removeAnnotationByCfi, removeAnnotations, setAnnotations, setInitialAnnotations, state.atEnd, state.atStart, state.currentLocation, state.meta, state.isLoading, state.isRendering, state.key, state.locations, state.progress, state.isSearching, state.searchResults, state.theme, state.totalLocations, state.annotations, setSection, setToc, setLandmarks, state.section, state.toc, state.landmarks, addBookmark, removeBookmark, removeBookmarks, updateBookmark, setBookmarks, state.bookmarks, state.isBookmarked, setIsBookmarked, injectJavascript, changeFlow, setFlow, state.flow]);
return /*#__PURE__*/_react.default.createElement(ReaderContext.Provider, {
value: contextValue
}, children);
}