Custom covers and open links in reader
This commit is contained in:
@@ -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",
|
||||
}}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ const GlobalSearchNovelCover = ({
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
novelCover && novelCover.startsWith("https")
|
||||
novelCover && !novelCover.startsWith("/")
|
||||
? novelCover
|
||||
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true",
|
||||
}}
|
||||
|
||||
@@ -9,7 +9,7 @@ const GlobalSearchNovelList = ({ data, theme, library, navigation }) => {
|
||||
(obj) => obj.novelUrl === novelUrl && obj.sourceId === sourceId
|
||||
);
|
||||
|
||||
const navigateToNovel = (item) => navigation.navigate("Novel", item);
|
||||
const navigateToNovel = (item) => navigation.push("Novel", item);
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<GlobalSearchNovelCover
|
||||
|
||||
@@ -54,6 +54,7 @@ import { SET_LAST_READ } from "../../redux/preferences/preference.types";
|
||||
import WebView from "react-native-webview";
|
||||
import { htmlToText } from "../../sources/helpers/htmlToText";
|
||||
import { setAppSettings } from "../../redux/settings/settings.actions";
|
||||
import { cleanHtml } from "../../sources/helpers/cleanHtml";
|
||||
|
||||
const Chapter = ({ route, navigation }) => {
|
||||
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: `
|
||||
<html>
|
||||
@@ -416,7 +436,7 @@ const Chapter = ({ route, navigation }) => {
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${chapter.chapterText.trim()}
|
||||
${cleanHtml(chapter.chapterText)}
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
|
||||
@@ -68,7 +68,7 @@ const HistoryItem = ({
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
novelCover && novelCover.startsWith("https")
|
||||
novelCover && !novelCover.startsWith("/")
|
||||
? novelCover
|
||||
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true",
|
||||
}}
|
||||
|
||||
+52
-10
@@ -23,6 +23,7 @@ import {
|
||||
markChaptersRead,
|
||||
markChapterUnreadAction,
|
||||
markPreviousChaptersReadAction,
|
||||
setNovel,
|
||||
sortAndFilterChapters,
|
||||
updateNovelAction,
|
||||
} from "../../redux/novel/novel.actions";
|
||||
@@ -42,6 +43,7 @@ import { Row } from "../../components/Common";
|
||||
import JumpToChapterModal from "./components/JumpToChapterModal";
|
||||
import { Actionbar } from "../../components/Actionbar/Actionbar";
|
||||
import EditInfoModal from "./components/EditInfoModal";
|
||||
import { setCustomNovelCover } from "../../database/queries/NovelQueries";
|
||||
|
||||
const Novel = ({ route, navigation }) => {
|
||||
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)}
|
||||
/>
|
||||
<IconButton
|
||||
icon="file-document-edit-outline"
|
||||
color="white"
|
||||
size={21}
|
||||
style={{
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
}}
|
||||
onPress={() => showEditInfoModal(true)}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
icon="share-variant"
|
||||
color="white"
|
||||
size={21}
|
||||
style={{
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
marginRight: 16,
|
||||
}}
|
||||
onPress={() =>
|
||||
Share.share({
|
||||
@@ -328,6 +322,54 @@ const Novel = ({ route, navigation }) => {
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Menu
|
||||
visible={extraMenu}
|
||||
onDismiss={() => showExtraMenu(false)}
|
||||
anchor={
|
||||
<IconButton
|
||||
icon="dots-vertical"
|
||||
color="white"
|
||||
size={21}
|
||||
style={{
|
||||
marginTop:
|
||||
StatusBar.currentHeight + 8,
|
||||
marginRight: 16,
|
||||
}}
|
||||
onPress={() => showExtraMenu(true)}
|
||||
/>
|
||||
}
|
||||
contentStyle={{
|
||||
backgroundColor: theme.menuColor,
|
||||
}}
|
||||
>
|
||||
<Menu.Item
|
||||
title="Edit info"
|
||||
style={{ backgroundColor: theme.menuColor }}
|
||||
titleStyle={{
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
onPress={() => showEditInfoModal(true)}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Edit cover"
|
||||
style={{ backgroundColor: theme.menuColor }}
|
||||
titleStyle={{
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
onPress={async () => {
|
||||
const cover = await setCustomNovelCover(
|
||||
novelId
|
||||
);
|
||||
|
||||
dispatch(
|
||||
setNovel({
|
||||
...novel,
|
||||
novelCover: cover,
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Menu>
|
||||
</Row>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -55,21 +55,28 @@ const NovelInfoHeader = ({
|
||||
<View>
|
||||
<CoverImage
|
||||
source={{
|
||||
uri:
|
||||
item.novelCover && item.novelCover.startsWith("https")
|
||||
uri: loading
|
||||
? item.novelCover && !item.novelCover.startsWith("/")
|
||||
? item.novelCover
|
||||
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true",
|
||||
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true"
|
||||
: novel.novelCover && !novel.novelCover.startsWith("/")
|
||||
? novel.novelCover
|
||||
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true",
|
||||
}}
|
||||
theme={theme}
|
||||
>
|
||||
<NovelInfoContainer>
|
||||
<NovelThumbnail
|
||||
source={{
|
||||
uri:
|
||||
item.novelCover &&
|
||||
item.novelCover.startsWith("https")
|
||||
uri: loading
|
||||
? item.novelCover &&
|
||||
!item.novelCover.startsWith("/")
|
||||
? item.novelCover
|
||||
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true",
|
||||
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true"
|
||||
: novel.novelCover &&
|
||||
!novel.novelCover.startsWith("/")
|
||||
? novel.novelCover
|
||||
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true",
|
||||
}}
|
||||
/>
|
||||
<View style={styles.novelDetails}>
|
||||
|
||||
@@ -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",
|
||||
}}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
|
||||
@@ -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.`;
|
||||
|
||||
@@ -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, "");
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -99,7 +99,9 @@ const htmlToText = (html, options = {}) => {
|
||||
* Decode HTML entities.
|
||||
*/
|
||||
|
||||
.replace(/&([^;]+);/g, decodeHtmlEntity);
|
||||
.replace(/&([^;]+);/g, decodeHtmlEntity)
|
||||
|
||||
.trim();
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user