forked from akai/readest
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 721e70d027 | |||
| 3c1a7eac86 | |||
| f8e21dbd4c | |||
| d16a35d26f | |||
| f881caf794 | |||
| eb6fa276de | |||
| e987c8b37a | |||
| 26d76e27ac | |||
| 975549fca0 | |||
| 9a6bed52dd |
@@ -50,6 +50,15 @@ const nextConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
source: '/_next/static/:path*',
|
||||
headers: [
|
||||
{
|
||||
key: 'Cache-Control',
|
||||
value: 'public, max-age=31536000, immutable',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
@@ -57,6 +66,8 @@ const nextConfig = {
|
||||
const withPWA = withPWAInit({
|
||||
dest: 'public',
|
||||
disable: isDev || appPlatform !== 'web',
|
||||
cacheStartUrl: false,
|
||||
dynamicStartUrl: false,
|
||||
cacheOnFrontEndNav: true,
|
||||
aggressiveFrontEndNavCaching: true,
|
||||
reloadOnOnline: true,
|
||||
@@ -66,6 +77,23 @@ const withPWA = withPWAInit({
|
||||
},
|
||||
workboxOptions: {
|
||||
disableDevLogs: true,
|
||||
manifestTransforms: [
|
||||
(manifestEntries) => {
|
||||
const manifest = manifestEntries.filter((entry) => {
|
||||
const url = entry.url;
|
||||
return (
|
||||
!url.includes('dynamic-css-manifest.json') &&
|
||||
!url.includes('middleware-manifest.json') &&
|
||||
!url.includes('react-loadable-manifest.json') &&
|
||||
!url.includes('build-manifest.json') &&
|
||||
!url.includes('_buildManifest.js') &&
|
||||
!url.includes('_ssgManifest.js') &&
|
||||
!url.includes('_headers')
|
||||
);
|
||||
});
|
||||
return { manifest };
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@readest/readest-app",
|
||||
"version": "0.9.92",
|
||||
"version": "0.9.93",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "dotenv -e .env.tauri -- next dev --turbopack",
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
/_next/static/*
|
||||
Cache-Control: public,max-age=31536000,immutable
|
||||
@@ -1,5 +1,15 @@
|
||||
{
|
||||
"releases": {
|
||||
"0.9.93": {
|
||||
"date": "2025-11-20",
|
||||
"notes": [
|
||||
"Sync: Improved reliability for new accounts so your books sync correctly from the start",
|
||||
"Library: Fixed an occasional issue where returning to the library could get stuck",
|
||||
"Layout: Improved image display in paginated mode so images won't exceed the available height",
|
||||
"TXT: Improved performance and stability when opening and parsing TXT files",
|
||||
"Fonts: Fixed an issue where monospace fonts were not applied correctly in code blocks"
|
||||
]
|
||||
},
|
||||
"0.9.92": {
|
||||
"date": "2025-11-18",
|
||||
"notes": [
|
||||
|
||||
@@ -99,7 +99,7 @@ export const useBooksSync = () => {
|
||||
oldBook.coverImageUrl = await appService?.generateCoverImageUrl(oldBook);
|
||||
}
|
||||
const mergedBook =
|
||||
matchingBook.updatedAt > oldBook.updatedAt
|
||||
matchingBook.updatedAt >= oldBook.updatedAt
|
||||
? { ...oldBook, ...matchingBook, syncedAt: Date.now() }
|
||||
: { ...matchingBook, ...oldBook, syncedAt: Date.now() };
|
||||
return mergedBook;
|
||||
|
||||
@@ -143,6 +143,10 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
sessionStorage.setItem('lastLibraryParams', searchParams?.toString() || '');
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
const doCheckAppUpdates = async () => {
|
||||
if (appService?.hasUpdater && settings.autoCheckUpdates) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useKOSync } from '../hooks/useKOSync';
|
||||
import {
|
||||
applyFixedlayoutStyles,
|
||||
applyImageStyle,
|
||||
applyScrollModeClass,
|
||||
applyTableStyle,
|
||||
applyThemeModeClass,
|
||||
applyTranslationStyle,
|
||||
@@ -193,6 +194,7 @@ const FoliateViewer: React.FC<{
|
||||
applyImageStyle(detail.doc);
|
||||
applyTableStyle(detail.doc);
|
||||
applyThemeModeClass(detail.doc, isDarkMode);
|
||||
applyScrollModeClass(detail.doc, viewSettings.scrolled || false);
|
||||
keepTextAlignment(detail.doc);
|
||||
removeTabIndex(detail.doc);
|
||||
|
||||
@@ -414,10 +416,17 @@ const FoliateViewer: React.FC<{
|
||||
applyFixedlayoutStyles(doc, viewSettings);
|
||||
}
|
||||
applyThemeModeClass(doc, isDarkMode);
|
||||
applyScrollModeClass(doc, viewSettings.scrolled || false);
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [themeCode, isDarkMode, viewSettings?.overrideColor, viewSettings?.invertImgColorInDark]);
|
||||
}, [
|
||||
themeCode,
|
||||
isDarkMode,
|
||||
viewSettings?.scrolled,
|
||||
viewSettings?.overrideColor,
|
||||
viewSettings?.invertImgColorInDark,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const mountCustomFonts = async () => {
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
import '@/utils/polyfill';
|
||||
import i18n from '@/i18n/i18n';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { IconContext } from 'react-icons';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { AuthProvider } from '@/context/AuthContext';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { CSPostHogProvider } from '@/context/PHContext';
|
||||
@@ -28,16 +27,6 @@ const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||
const iconSize = useDefaultIconSize();
|
||||
useSafeAreaInsets(); // Initialize safe area insets
|
||||
|
||||
const pathname = usePathname();
|
||||
const prevPathnameRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevPathnameRef.current !== null) {
|
||||
sessionStorage.setItem('lastPath', prevPathnameRef.current);
|
||||
}
|
||||
prevPathnameRef.current = pathname;
|
||||
}, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
const handlerLanguageChanged = (lng: string) => {
|
||||
document.documentElement.lang = lng;
|
||||
|
||||
@@ -108,8 +108,9 @@ export function useSync(bookKey?: string) {
|
||||
const result = await syncClient.pullChanges(since, type, bookId, metaHash);
|
||||
setSyncResult({ ...syncResult, [type]: result[type] });
|
||||
const records = result[type];
|
||||
if (!records?.length) return;
|
||||
const maxTime = computeMaxTimestamp(records);
|
||||
if (since > 0 && !records?.length) return;
|
||||
// For since = 0, we set lastSyncedAt to now if no records returned
|
||||
const maxTime = records?.length ? computeMaxTimestamp(records) : Date.now();
|
||||
setLastSyncedAt(maxTime);
|
||||
|
||||
// due to closures in React hooks the settings might be stale
|
||||
|
||||
@@ -82,10 +82,10 @@ export const navigateToLibrary = (
|
||||
navOptions?: { scroll?: boolean },
|
||||
navBack?: boolean,
|
||||
) => {
|
||||
const lastPath = typeof window !== 'undefined' ? sessionStorage.getItem('lastPath') : null;
|
||||
if (navBack && lastPath === '/library') {
|
||||
router.back();
|
||||
return;
|
||||
const lastLibraryParams =
|
||||
typeof window !== 'undefined' ? sessionStorage.getItem('lastLibraryParams') : null;
|
||||
if (navBack && lastLibraryParams) {
|
||||
queryParams = lastLibraryParams;
|
||||
}
|
||||
|
||||
router.replace(`/library${queryParams ? `?${queryParams}` : ''}`, navOptions);
|
||||
|
||||
@@ -100,7 +100,7 @@ const getFontStyles = (
|
||||
pre, code, kbd {
|
||||
font-family: var(--monospace);
|
||||
}
|
||||
body *:not(pre):not(code):not(kbd):not(pre *):not(code *):not(kbd *) {
|
||||
body *:not(pre, code, kbd, .code):not(pre *, code *, kbd *, .code *) {
|
||||
${overrideFont ? 'font-family: revert !important;' : ''}
|
||||
}
|
||||
`;
|
||||
@@ -364,6 +364,10 @@ const getLayoutStyles = (
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
body.paginated-mode td:has(img), body.paginated-mode td :has(img) {
|
||||
max-height: calc(var(--available-height) * 0.8 * 1px);
|
||||
}
|
||||
|
||||
/* some epubs set insane inline-block for p */
|
||||
p {
|
||||
display: block;
|
||||
@@ -654,6 +658,11 @@ export const applyThemeModeClass = (document: Document, isDarkMode: boolean) =>
|
||||
document.body.classList.add(isDarkMode ? 'theme-dark' : 'theme-light');
|
||||
};
|
||||
|
||||
export const applyScrollModeClass = (document: Document, isScrollMode: boolean) => {
|
||||
document.body.classList.remove('scroll-mode', 'paginated-mode');
|
||||
document.body.classList.add(isScrollMode ? 'scroll-mode' : 'paginated-mode');
|
||||
};
|
||||
|
||||
export const applyImageStyle = (document: Document) => {
|
||||
document.querySelectorAll('img').forEach((img) => {
|
||||
const parent = img.parentNode;
|
||||
@@ -708,9 +717,11 @@ export const applyTableStyle = (document: Document) => {
|
||||
}
|
||||
}
|
||||
|
||||
const scale = `calc(min(1, var(--available-width) / ${totalTableWidth}))`;
|
||||
table.style.transformOrigin = 'left top';
|
||||
table.style.transform = `scale(${scale})`;
|
||||
if (totalTableWidth > 0) {
|
||||
const scale = `calc(min(1, var(--available-width) / ${totalTableWidth}))`;
|
||||
table.style.transformOrigin = 'left top';
|
||||
table.style.transform = `scale(${scale})`;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ export class TxtToEpubConverter {
|
||||
|
||||
const fileContent = await txtFile.arrayBuffer();
|
||||
const detectedEncoding = this.detectEncoding(fileContent) || 'utf-8';
|
||||
console.log(`Detected encoding: ${detectedEncoding}`);
|
||||
const decoder = new TextDecoder(detectedEncoding);
|
||||
const txtContent = decoder.decode(fileContent).trim();
|
||||
|
||||
@@ -384,6 +385,7 @@ export class TxtToEpubConverter {
|
||||
}
|
||||
|
||||
const tocManifest = `<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/>`;
|
||||
const styleManifest = `<item id="css" href="style.css" media-type="text/css"/>`;
|
||||
|
||||
// Add content.opf file
|
||||
const contentOpf = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -397,6 +399,7 @@ export class TxtToEpubConverter {
|
||||
<manifest>
|
||||
${manifest}
|
||||
${tocManifest}
|
||||
${styleManifest}
|
||||
</manifest>
|
||||
<spine toc="ncx">
|
||||
${spine}
|
||||
@@ -413,6 +416,31 @@ export class TxtToEpubConverter {
|
||||
new TextDecoder('utf-8', { fatal: true }).decode(buffer);
|
||||
return 'utf-8';
|
||||
} catch {
|
||||
const uint8Array = new Uint8Array(buffer);
|
||||
// Try tolerant UTF-8 detection - check if most of it is valid UTF-8
|
||||
let validBytes = 0;
|
||||
let checkedBytes = 0;
|
||||
const sampleSize = Math.min(uint8Array.length, 10000);
|
||||
|
||||
for (let i = 0; i < sampleSize; i++) {
|
||||
try {
|
||||
new TextDecoder('utf-8', { fatal: true }).decode(uint8Array.slice(i, i + 100));
|
||||
validBytes += 100;
|
||||
checkedBytes += 100;
|
||||
i += 99;
|
||||
} catch {
|
||||
checkedBytes++;
|
||||
}
|
||||
}
|
||||
|
||||
const validPercentage = (validBytes / checkedBytes) * 100;
|
||||
console.log(`UTF-8 validity: ${validPercentage.toFixed(2)}%`);
|
||||
|
||||
// If more than 80% is valid UTF-8, consider it UTF-8 with some corruption
|
||||
if (validPercentage > 80) {
|
||||
console.log('Treating as UTF-8 despite some invalid sequences');
|
||||
return 'utf-8';
|
||||
}
|
||||
// If UTF-8 decoding fails, try to detect other encodings
|
||||
}
|
||||
|
||||
|
||||
+1
-1
Submodule packages/foliate-js updated: f6bccb509d...56f9822fb4
Reference in New Issue
Block a user