diff --git a/src/components/NovelCover.js b/src/components/NovelCover.js index 781cb111..093a122d 100644 --- a/src/components/NovelCover.js +++ b/src/components/NovelCover.js @@ -67,7 +67,7 @@ const NovelCover = ({ item, onPress, libraryStatus, theme }) => { source={{ uri: item.novelCover && - item.novelCover.startsWith("https") + !item.novelCover.startsWith("/") ? item.novelCover : "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true", }} diff --git a/src/database/queries/NovelQueries.js b/src/database/queries/NovelQueries.js index f7f337c7..cc94a8d3 100644 --- a/src/database/queries/NovelQueries.js +++ b/src/database/queries/NovelQueries.js @@ -2,11 +2,13 @@ import * as SQLite from "expo-sqlite"; const db = SQLite.openDatabase("lnreader.db"); import BackgroundService from "react-native-background-actions"; +import * as DocumentPicker from "expo-document-picker"; import { fetchChapters, fetchNovel } from "../../services/Source/source"; import { insertChapters } from "./ChapterQueries"; import { showToast } from "../../hooks/showToast"; +import { setNovel } from "../../redux/novel/novel.actions"; const insertNovelQuery = `INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; @@ -211,3 +213,20 @@ export const updateNovelInfo = async (info, novelId) => { ); }); }; + +export const setCustomNovelCover = async (novelId) => { + const image = await DocumentPicker.getDocumentAsync({ type: "image/*" }); + + if (image.uri) { + db.transaction((tx) => { + tx.executeSql( + "UPDATE novels SET novelCover = ? WHERE novelId = ?", + [image.uri, novelId], + (txObj, res) => {}, + (txObj, error) => console.log("Error ", error) + ); + }); + + return image.uri; + } +}; diff --git a/src/screens/Browse/globalsearch/GlobalSearchNovelCover.js b/src/screens/Browse/globalsearch/GlobalSearchNovelCover.js index d6b1eeef..2d1baca2 100644 --- a/src/screens/Browse/globalsearch/GlobalSearchNovelCover.js +++ b/src/screens/Browse/globalsearch/GlobalSearchNovelCover.js @@ -22,7 +22,7 @@ const GlobalSearchNovelCover = ({ { (obj) => obj.novelUrl === novelUrl && obj.sourceId === sourceId ); - const navigateToNovel = (item) => navigation.navigate("Novel", item); + const navigateToNovel = (item) => navigation.push("Novel", item); const renderItem = ({ item }) => ( { const { @@ -370,11 +371,30 @@ const Chapter = ({ route, navigation }) => { backgroundColor: readerBackground( reader.theme ), + paddingBottom: 24, }} originWhitelist={["*"]} scalesPageToFit={true} showsVerticalScrollIndicator={false} onScroll={onScroll} + onNavigationStateChange={async ({ + url, + }) => { + if ( + (sourceId === 50 || + sourceId == 62) && + url !== "about:blank" + ) { + setLoading(true); + const res = await fetchChapter( + sourceId, + novelUrl, + url + ); + setChapter(res); + setLoading(false); + } + }} source={{ html: ` @@ -416,7 +436,7 @@ const Chapter = ({ route, navigation }) => { - ${chapter.chapterText.trim()} + ${cleanHtml(chapter.chapterText)} `, diff --git a/src/screens/History/HistoryItem.js b/src/screens/History/HistoryItem.js index 43642674..ff64ce87 100644 --- a/src/screens/History/HistoryItem.js +++ b/src/screens/History/HistoryItem.js @@ -68,7 +68,7 @@ const HistoryItem = ({ { const item = route.params; @@ -54,6 +56,7 @@ const Novel = ({ route, navigation }) => { const [selected, setSelected] = useState([]); const [downloadMenu, showDownloadMenu] = useState(false); + const [extraMenu, showExtraMenu] = useState(false); let flatlistRef = useRef(null); let chaptersSettingsSheetRef = useRef(null); @@ -305,22 +308,13 @@ const Novel = ({ route, navigation }) => { }} onPress={() => showJumpToChapterModal(true)} /> - showEditInfoModal(true)} - /> + Share.share({ @@ -328,6 +322,54 @@ const Novel = ({ route, navigation }) => { }) } /> + showExtraMenu(false)} + anchor={ + showExtraMenu(true)} + /> + } + contentStyle={{ + backgroundColor: theme.menuColor, + }} + > + showEditInfoModal(true)} + /> + { + const cover = await setCustomNovelCover( + novelId + ); + + dispatch( + setNovel({ + ...novel, + novelCover: cover, + }) + ); + }} + /> + )} diff --git a/src/screens/Novel/components/NovelHeader.js b/src/screens/Novel/components/NovelHeader.js index 180e6cbb..e8f32725 100644 --- a/src/screens/Novel/components/NovelHeader.js +++ b/src/screens/Novel/components/NovelHeader.js @@ -55,21 +55,28 @@ const NovelInfoHeader = ({ diff --git a/src/screens/Updates/components/UpdatesItem.js b/src/screens/Updates/components/UpdatesItem.js index f3e22674..86717f1e 100644 --- a/src/screens/Updates/components/UpdatesItem.js +++ b/src/screens/Updates/components/UpdatesItem.js @@ -29,7 +29,7 @@ const UpdateCard = ({ source={{ uri: item.novelCover && - item.novelCover.startsWith("http") + !item.novelCover.startsWith("/") ? item.novelCover : "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true", }} diff --git a/src/services/backup.js b/src/services/backup.js index fb7a3d65..6a466582 100644 --- a/src/services/backup.js +++ b/src/services/backup.js @@ -6,7 +6,6 @@ import { restoreLibrary } from "../database/queries/NovelQueries"; import { showToast } from "../hooks/showToast"; import * as Notifications from "expo-notifications"; -import BackgroundService from "react-native-background-actions"; Notifications.setNotificationHandler({ handleNotification: async () => { diff --git a/src/sources/en/lightnovelpub.js b/src/sources/en/lightnovelpub.js index 2a81a764..76a521e4 100644 --- a/src/sources/en/lightnovelpub.js +++ b/src/sources/en/lightnovelpub.js @@ -85,7 +85,7 @@ const parseNovelAndChapters = async (novelUrl) => { .replace(/[\t\n]/g, "") .trim(); - novel.novelCover = $("figure.cover > img").attr("src"); + novel.novelCover = $("figure.cover > img").attr("data-src"); novel.genre = ""; diff --git a/src/sources/en/novelupdates.js b/src/sources/en/novelupdates.js index 1be69fd8..db68dd8c 100644 --- a/src/sources/en/novelupdates.js +++ b/src/sources/en/novelupdates.js @@ -80,7 +80,7 @@ const parseNovelAndChapters = async (novelUrl) => { let summary = $("#editdescription").text().trim(); - novel.summary = summary + `\nType: ${type}`; + novel.summary = summary + `\n\nType: ${type}`; let novelChapters = []; @@ -119,6 +119,13 @@ const parseNovelAndChapters = async (novelUrl) => { return novel; }; +function getLocation(href) { + var match = href.match( + /^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/ + ); + return match && `${match[1]}//${match[3]}`; +} + const parseChapter = async (novelUrl, chapterUrl) => { const url = chapterUrl; @@ -135,6 +142,8 @@ const parseChapter = async (novelUrl, chapterUrl) => { }); body = await result.text(); + // console.log(result.url); + // console.log("Redirected URL: ", result.url); $ = cheerio.load(body); @@ -173,6 +182,8 @@ const parseChapter = async (novelUrl, chapterUrl) => { } else if (isTumblr) { chapterText = $(".post").html(); } else if (isBlogspot) { + $(".post-share-buttons").remove(); + chapterText = $(".entry-content").html(); } else if (isHostedNovel) { chapterText = $(".chapter").html(); @@ -196,6 +207,7 @@ const parseChapter = async (novelUrl, chapterUrl) => { ".post-cats", ".sidebar", ".author-avatar", + ".ezoic-ad", ]; bloatClasses.map((tag) => $(tag).remove()); @@ -228,6 +240,14 @@ const parseChapter = async (novelUrl, chapterUrl) => { if (!chapterText) { chapterText = "Chapter not available.\n\nReport if it's available in webview."; + } else { + /** + * Convert relative urls to absolute + */ + chapterText = chapterText.replace( + /href="\//g, + `href="${getLocation(result.url)}/` + ); } } catch (error) { chapterText = `Chapter not available (Error: ${error.message}).\n\nReport if it's available in webview.`; diff --git a/src/sources/en/wuxiaworld.js b/src/sources/en/wuxiaworld.js index d63bcf0b..749268d6 100644 --- a/src/sources/en/wuxiaworld.js +++ b/src/sources/en/wuxiaworld.js @@ -115,6 +115,8 @@ const parseChapter = async (novelUrl, chapterUrl) => { $ = cheerio.load(body); + $(".chapter-nav").remove(); + let chapterName = $("#sidebar-toggler-container").next().text(); chapterName = chapterName.replace(/[\t\n]/g, ""); diff --git a/src/sources/helpers/cleanHtml.js b/src/sources/helpers/cleanHtml.js new file mode 100644 index 00000000..62cb2897 --- /dev/null +++ b/src/sources/helpers/cleanHtml.js @@ -0,0 +1,16 @@ +export const cleanHtml = (html) => { + let text = html; + + text = text + .replace(/(font-family|line-height|font-size|text-align):.*?;/gi, "") + .replace(/class=".*?"/gi, "") + .replace(//gim, "") + .replace(/<\s*script[^>]*>[\s\S]*?<\/script>/gim, "") + .replace(/<\s*noscript[^>]*>[\s\S]*?<\/noscript>/gim, "") + .replace(/<\s*form[^>]*>[\s\S]*?<\/form>/gim, "") + .trim(); + + // console.log(text); + + return text; +}; diff --git a/src/sources/helpers/htmlToText.js b/src/sources/helpers/htmlToText.js index 55ecf2e0..49e49af5 100644 --- a/src/sources/helpers/htmlToText.js +++ b/src/sources/helpers/htmlToText.js @@ -99,7 +99,9 @@ const htmlToText = (html, options = {}) => { * Decode HTML entities. */ - .replace(/&([^;]+);/g, decodeHtmlEntity); + .replace(/&([^;]+);/g, decodeHtmlEntity) + + .trim(); return text; }; diff --git a/src/theme/theme.js b/src/theme/theme.js index adfafb1a..1941111e 100644 --- a/src/theme/theme.js +++ b/src/theme/theme.js @@ -150,7 +150,7 @@ export const yinYangTheme = { ...PrimaryColors.dark, colorAccent: AccentColors.yinYang, rippleColor: RippleColors.yinYang, - searchBarColor: "#484861", + searchBarColor: "#303135", menuColor: "#484861", filterColor: "#FFEB3B", dividerColor: DividerColors.DARK,