forked from akai/readest
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f11f437e1 | |||
| 5773e31990 | |||
| 9dda5d4c88 | |||
| 7f5b6959e1 | |||
| 126981d085 | |||
| 854a578929 | |||
| 6dd0daa597 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@readest/readest-app",
|
||||
"version": "0.9.86",
|
||||
"version": "0.9.87",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "dotenv -e .env.tauri -- next dev --turbopack",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
{
|
||||
"releases": {
|
||||
"0.9.87": {
|
||||
"date": "2025-10-16",
|
||||
"notes": [
|
||||
"Reader: Improved chapter detection with better chapter title recognition",
|
||||
"Reader: Enhanced compatibility for parsing more footnote formats",
|
||||
"Theme: Fixed auto theme switching in new reader windows",
|
||||
"TTS: Improved scrolling accuracy to highlighted text during read-aloud",
|
||||
"TTS: Fixed crash issues on certain Android systems"
|
||||
]
|
||||
},
|
||||
"0.9.86": {
|
||||
"date": "2025-10-14",
|
||||
"notes": [
|
||||
|
||||
+38
-1
@@ -161,10 +161,47 @@ class MediaPlaybackService : MediaBrowserServiceCompat() {
|
||||
setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
setSmallIcon(R.drawable.notification_icon)
|
||||
|
||||
addAction(
|
||||
android.R.drawable.ic_media_previous,
|
||||
"Previous",
|
||||
MediaButtonReceiver.buildMediaButtonPendingIntent(
|
||||
this@MediaPlaybackService,
|
||||
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
|
||||
)
|
||||
)
|
||||
if (playbackState == PlaybackStateCompat.STATE_PLAYING) {
|
||||
addAction(
|
||||
android.R.drawable.ic_media_pause,
|
||||
"Pause",
|
||||
MediaButtonReceiver.buildMediaButtonPendingIntent(
|
||||
this@MediaPlaybackService,
|
||||
PlaybackStateCompat.ACTION_PAUSE
|
||||
)
|
||||
)
|
||||
} else {
|
||||
addAction(
|
||||
android.R.drawable.ic_media_play,
|
||||
"Play",
|
||||
MediaButtonReceiver.buildMediaButtonPendingIntent(
|
||||
this@MediaPlaybackService,
|
||||
PlaybackStateCompat.ACTION_PLAY
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
addAction(
|
||||
android.R.drawable.ic_media_next,
|
||||
"Next",
|
||||
MediaButtonReceiver.buildMediaButtonPendingIntent(
|
||||
this@MediaPlaybackService,
|
||||
PlaybackStateCompat.ACTION_SKIP_TO_NEXT
|
||||
)
|
||||
)
|
||||
|
||||
setStyle(
|
||||
androidx.media.app.NotificationCompat.MediaStyle()
|
||||
.setMediaSession(mediaSession?.sessionToken)
|
||||
.setShowActionsInCompactView(0)
|
||||
.setShowActionsInCompactView(0, 1, 2)
|
||||
)
|
||||
}
|
||||
return builder.build()
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
"default-src": "'self' 'unsafe-inline' blob: data: customprotocol: asset: http://asset.localhost ipc: http://ipc.localhost",
|
||||
"connect-src": "'self' blob: data: asset: http://asset.localhost ipc: http://ipc.localhost https://*.sentry.io https://*.posthog.com https://*.deepl.com https://*.wikipedia.org https://*.wiktionary.org https://*.supabase.co https://*.readest.com wss://speech.platform.bing.com https://*.cloudflarestorage.com https://translate.googleapis.com https://translate.toil.cc https://*.microsofttranslator.com https://edge.microsoft.com https://*.googleusercontent.com",
|
||||
"img-src": "'self' blob: data: asset: http://asset.localhost https://*",
|
||||
"style-src": "'self' 'unsafe-inline' blob: asset: http://asset.localhost https://cdn.jsdelivr.net https://fonts.googleapis.com https://chinese-fonts-cdn.netlify.app",
|
||||
"font-src": "'self' blob: data: asset: http://asset.localhost tauri: https://db.onlinewebfonts.com https://cdn.jsdelivr.net https://fonts.gstatic.com https://chinese-fonts-cdn.netlify.app",
|
||||
"style-src": "'self' 'unsafe-inline' blob: asset: http://asset.localhost https://cdn.jsdelivr.net https://fonts.googleapis.com https://chinese-fonts-cdn.netlify.app https://cdnjs.cloudflare.com",
|
||||
"font-src": "'self' blob: data: asset: http://asset.localhost tauri: https://db.onlinewebfonts.com https://cdn.jsdelivr.net https://fonts.gstatic.com https://chinese-fonts-cdn.netlify.app https://cdnjs.cloudflare.com",
|
||||
"frame-src": "'self' blob: asset: http://asset.localhost https://*.stripe.com",
|
||||
"script-src": "'self' 'unsafe-inline' 'unsafe-eval' blob: asset: http://asset.localhost https://*.sentry.io https://*.posthog.com https://*.stripe.com"
|
||||
},
|
||||
|
||||
@@ -68,8 +68,8 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
|
||||
}
|
||||
: {
|
||||
top: `${topInset}px`,
|
||||
insetInlineStart: `calc(${horizontalGap / 2}% + ${contentInsets.left}px)`,
|
||||
width: `calc(100% - ${contentInsets.left + contentInsets.right}px)`,
|
||||
paddingInlineStart: `calc(${horizontalGap / 2}% + ${contentInsets.left}px)`,
|
||||
width: '100%',
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { BookSearchConfig, BookSearchResult } from '@/types/book';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { debounce } from '@/utils/debounce';
|
||||
import { isCJKStr } from '@/utils/lang';
|
||||
import { createRejecttFilter } from '@/utils/node';
|
||||
import { createRejectFilter } from '@/utils/node';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import SearchOptions from './SearchOptions';
|
||||
|
||||
@@ -127,7 +127,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
|
||||
...searchConfig,
|
||||
index,
|
||||
query: term,
|
||||
acceptNode: createRejecttFilter({
|
||||
acceptNode: createRejectFilter({
|
||||
tags: primaryLang.startsWith('ja') ? ['rt'] : [],
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -208,6 +208,30 @@ const TTSControl: React.FC<TTSControlProps> = ({ bookKey, gridInsets }) => {
|
||||
viewSettings.ttsLocation = cfi || '';
|
||||
setViewSettings(bookKey, viewSettings);
|
||||
}
|
||||
|
||||
if (!view.renderer.scrolled) {
|
||||
view.renderer.scrollToAnchor(range);
|
||||
} else {
|
||||
const rect = range.getBoundingClientRect();
|
||||
const { start, size, viewSize, sideProp } = view.renderer;
|
||||
const positionStart = rect[sideProp === 'height' ? 'y' : 'x'] + viewSettings.marginTopPx;
|
||||
const positionEnd = rect[sideProp === 'height' ? 'height' : 'width'] + positionStart;
|
||||
const offsetStart = view.book.dir === 'rtl' ? viewSize - positionStart : positionStart;
|
||||
const offsetEnd = view.book.dir === 'rtl' ? viewSize - positionEnd : positionEnd;
|
||||
|
||||
const showHeader = viewSettings.showHeader;
|
||||
const showFooter = viewSettings.showFooter;
|
||||
const showBarsOnScroll = viewSettings.showBarsOnScroll;
|
||||
const headerScrollOverlap = showHeader && showBarsOnScroll ? 44 : 0;
|
||||
const footerScrollOverlap = showFooter && showBarsOnScroll ? 44 : 0;
|
||||
const scrollingOverlap = viewSettings.scrollingOverlap;
|
||||
const endInNextView = offsetEnd > start + size - footerScrollOverlap - scrollingOverlap;
|
||||
const startInPrevView = offsetStart < start + headerScrollOverlap + scrollingOverlap;
|
||||
if (endInNextView || startInPrevView) {
|
||||
const scrollTo = offsetStart - headerScrollOverlap - scrollingOverlap;
|
||||
view.renderer.scrollToAnchor(scrollTo / viewSize);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ttsController.addEventListener('tts-speak-mark', handleSpeakMark);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AppService } from '@/types/system';
|
||||
import { parseSSMLMarks } from '@/utils/ssml';
|
||||
import { Overlayer } from 'foliate-js/overlayer.js';
|
||||
import { TTSGranularity, TTSHighlightOptions, TTSMark, TTSVoice } from './types';
|
||||
import { createRejecttFilter } from '@/utils/node';
|
||||
import { createRejectFilter } from '@/utils/node';
|
||||
import { WebSpeechClient } from './WebSpeechClient';
|
||||
import { NativeTTSClient } from './NativeTTSClient';
|
||||
import { EdgeTTSClient } from './EdgeTTSClient';
|
||||
@@ -85,13 +85,6 @@ export class TTSController extends EventTarget {
|
||||
const { style, color } = options;
|
||||
overlayer?.remove(HIGHLIGHT_KEY);
|
||||
overlayer?.add(HIGHLIGHT_KEY, range, Overlayer[style], { color });
|
||||
const rect = range.getBoundingClientRect();
|
||||
const { start, size, viewSize, sideProp } = this.view.renderer;
|
||||
const position = rect[sideProp === 'height' ? 'y' : 'x'] + 88;
|
||||
const offset = this.view.book.dir === 'rtl' ? viewSize - position : position;
|
||||
if (!this.view.renderer.scrolled || offset < start || offset > start + size) {
|
||||
this.view.renderer.scrollToAnchor(range);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,7 +102,7 @@ export class TTSController extends EventTarget {
|
||||
const highlightOptions: TTSHighlightOptions = { style: 'highlight', color: 'gray' };
|
||||
await this.view.initTTS(
|
||||
granularity,
|
||||
createRejecttFilter({
|
||||
createRejectFilter({
|
||||
tags: ['rt', 'sup'],
|
||||
contents: [{ tag: 'a', content: /^\d+$/ }],
|
||||
}),
|
||||
|
||||
@@ -141,7 +141,12 @@ export const loadDataTheme = () => {
|
||||
const themeMode = localStorage.getItem('themeMode');
|
||||
const themeColor = localStorage.getItem('themeColor');
|
||||
if (themeMode && themeColor) {
|
||||
document.documentElement.setAttribute('data-theme', `${themeColor}-${themeMode}`);
|
||||
const systemIsDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const isDarkMode = themeMode === 'dark' || (themeMode === 'auto' && systemIsDarkMode);
|
||||
document.documentElement.setAttribute(
|
||||
'data-theme',
|
||||
`${themeColor}-${isDarkMode ? 'dark' : 'light'}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ const getAdditionalBasicFontLinks = () => `
|
||||
|
||||
const getAdditionalCJKFontLinks = () => `
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/misans-webfont@1.0.4/misans-l3/misans-l3/result.min.css" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cn-fontsource-lxgw-wen-kai-gb-screen@1.0.6/font.min.css" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lxgw-wenkai-screen-web/1.520.0/lxgwwenkaigbscreen/result.css" crossorigin="anonymous" />
|
||||
<link rel='stylesheet' href='https://chinese-fonts-cdn.netlify.app/packages/hwmct/dist/%E6%B1%87%E6%96%87%E6%98%8E%E6%9C%9D%E4%BD%93/result.css' crossorigin="anonymous" />
|
||||
<link rel='stylesheet' href='https://chinese-fonts-cdn.netlify.app/packages/jhlst/dist/%E4%BA%AC%E8%8F%AF%E8%80%81%E5%AE%8B%E4%BD%93v2_002/result.css' crossorigin="anonymous" />
|
||||
<link rel='stylesheet' href='https://chinese-fonts-cdn.netlify.app/packages/syst/dist/SourceHanSerifCN/result.css' crossorigin="anonymous" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const createRejecttFilter = ({
|
||||
export const createRejectFilter = ({
|
||||
tags = [],
|
||||
classes = [],
|
||||
attributes = [],
|
||||
|
||||
@@ -115,9 +115,10 @@ export class TxtToEpubConverter {
|
||||
[
|
||||
String.raw`第[零〇一二三四五六七八九十0-9][零〇一二三四五六七八九十百千万0-9]*(?:[章卷节回讲篇封本册部话])(?:[::、 \(\)0-9]*[^\n-]{0,24})(?!\S)`,
|
||||
String.raw`(?:楔子|前言|简介|引言|序言|序章|总论|概论|后记)(?:[:: ][^\n-]{0,24})?(?!\S)`,
|
||||
String.raw`chapter[\s.]*[0-9]+(?:[::. ]+[^\n-]{0,50})?(?!\S)`,
|
||||
].join('|') +
|
||||
')',
|
||||
'gu',
|
||||
'gui',
|
||||
),
|
||||
);
|
||||
chapterRegexps.push(
|
||||
|
||||
+1
-1
Submodule packages/foliate-js updated: e900daa533...a00119cbcf
Reference in New Issue
Block a user