forked from akai/readest
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2640359a0 | |||
| 079aeaedb0 | |||
| bfacadb964 | |||
| 2e98ed44ee | |||
| 952d2a3553 | |||
| 225aa0ca59 | |||
| 37f718b84b | |||
| db8f53a6e1 | |||
| ce345ad67f | |||
| 8e53e625a4 |
@@ -11,7 +11,7 @@ jobs:
|
||||
env:
|
||||
RUSTFLAGS: '-C target-cpu=skylake'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: 'true'
|
||||
- name: Install minimal stable with clippy and rustfmt
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
- platform: 'web'
|
||||
- platform: 'tauri'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
release_version: ${{ steps.get-release-notes.outputs.release_version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v4
|
||||
- name: get version
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: initialize git submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
build_and_deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: 'true'
|
||||
- uses: amondnet/vercel-action@v25
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@readest/readest-app",
|
||||
"version": "0.9.71",
|
||||
"version": "0.9.72",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "dotenv -e .env.tauri -- next dev",
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
"Slow": "Повільно",
|
||||
"Solarized": "Сонячний",
|
||||
"Speak": "Озвучити",
|
||||
"Subjects": "Теми",
|
||||
"Subjects": "Жанри",
|
||||
"System Fonts": "Системні шрифти",
|
||||
"Theme Color": "Колір теми",
|
||||
"Theme Mode": "Режим теми",
|
||||
@@ -469,26 +469,26 @@
|
||||
"Disable Double Click": "Вимкнути подвійне натискання",
|
||||
"TTS not supported for this document": "TTS не підтримується в цьому документі",
|
||||
"Reset Password": "Скинути пароль",
|
||||
"Show Reading Progress": "Показати прогрес читання",
|
||||
"Reading Progress Style": "Стиль прогресу читання",
|
||||
"Show Reading Progress": "Показати проґрес читання",
|
||||
"Reading Progress Style": "Стиль проґресу читання",
|
||||
"Page Number": "Номер сторінки",
|
||||
"Percentage": "Процент",
|
||||
"Percentage": "Відсотки",
|
||||
"Deleted local copy of the book: {{title}}": "Видалено локальну копію книги: {{title}}",
|
||||
"Failed to delete cloud backup of the book: {{title}}": "Не вдалося видалити резервну копію книги в хмарі: {{title}}",
|
||||
"Failed to delete local copy of the book: {{title}}": "Не вдалося видалити локальну копію книги: {{title}}",
|
||||
"Are you sure to delete the local copy of the selected book?": "Ви впевнені, що хочете видалити локальну копію вибраної книги?",
|
||||
"Remove from Cloud & Device": "Видалити з хмари та пристрою",
|
||||
"Remove from Cloud Only": "Видалити тільки з хмари",
|
||||
"Remove from Device Only": "Видалити тільки з пристрою",
|
||||
"Disconnected": "Відключено",
|
||||
"Remove from Cloud Only": "Видалити тільки із хмари",
|
||||
"Remove from Device Only": "Видалити тільки із пристрою",
|
||||
"Disconnected": "Від'єднано",
|
||||
"KOReader Sync Settings": "Налаштування синхронізації KOReader",
|
||||
"Sync Strategy": "Стратегія синхронізації",
|
||||
"Ask on conflict": "Запитувати при конфлікті",
|
||||
"Always use latest": "Завжди використовувати останню версію",
|
||||
"Always use latest": "Завжди використовувати найновішу",
|
||||
"Send changes only": "Відправити тільки зміни",
|
||||
"Receive changes only": "Отримати тільки зміни",
|
||||
"Disabled": "Вимкнено",
|
||||
"Checksum Method": "Метод контрольної суми",
|
||||
"Checksum Method": "Метод контролю суми",
|
||||
"File Content (recommended)": "Вміст файлу (рекомендується)",
|
||||
"File Name": "Ім'я файлу",
|
||||
"Device Name": "Ім'я пристрою",
|
||||
@@ -498,19 +498,19 @@
|
||||
"Username": "Ім'я користувача",
|
||||
"Your Username": "Ваше ім'я користувача",
|
||||
"Password": "Пароль",
|
||||
"Connect": "Підключитися",
|
||||
"Connect": "Під'єднатися",
|
||||
"KOReader Sync": "Синхронізація KOReader",
|
||||
"Sync Conflict": "Конфлікт синхронізації",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "Синхронізувати прогрес читання з \"{{deviceName}}\"?",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "Синхронізувати прогрес читання із \"{{deviceName}}\"?",
|
||||
"another device": "інший пристрій",
|
||||
"Local Progress": "Локальний прогрес",
|
||||
"Remote Progress": "Віддалений прогрес",
|
||||
"Remote Progress": "Прогрес у хмарі",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "Сторінка {{page}} з {{total}} ({{percentage}}%)",
|
||||
"Current position": "Поточна позиція",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "Приблизно сторінка {{page}} з {{total}} ({{percentage}}%)",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "Приблизно сторінка {{page}} із {{total}} ({{percentage}}%)",
|
||||
"Approximately {{percentage}}%": "Приблизно {{percentage}}%",
|
||||
"Failed to connect": "Не вдалося підключитися",
|
||||
"Sync Server Connected": "Сервер синхронізації підключено",
|
||||
"Failed to connect": "Не вдалося під'єднатися",
|
||||
"Sync Server Connected": "Сервер синхронізації під'єднано",
|
||||
"Precision: {{precision}} digits after the decimal": "Точність: {{precision}} знаків після коми",
|
||||
"Sync as {{userDisplayName}}": "Синхронізувати як {{userDisplayName}}"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
{
|
||||
"releases": {
|
||||
"0.9.72": {
|
||||
"date": "2025-08-18",
|
||||
"notes": [
|
||||
"Fixed Edge TTS voice playback for EPUBs",
|
||||
"Reduced accidental page flips when toggling toolbars",
|
||||
"Fixed images with background colors not displaying correctly in some books",
|
||||
"Added support for custom KOReader Sync Servers on your local network (LAN)"
|
||||
]
|
||||
},
|
||||
"0.9.71": {
|
||||
"date": "2025-08-13",
|
||||
"notes": [
|
||||
"Sync: Added two ways to sync reading progress with Koreader devices",
|
||||
"Sync: Added two ways to sync reading progress with KOReader devices",
|
||||
"EPUB: Applied monospace font settings",
|
||||
"EPUB: Fixed initial text alignment for some EPUB files",
|
||||
"PDF: Fixed issue opening large PDF files on Android",
|
||||
|
||||
@@ -41,7 +41,7 @@ tauri-plugin-log = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-os = "2"
|
||||
tauri-plugin-http = "2"
|
||||
tauri-plugin-http = { version = "2", features = ["dangerous-settings"] }
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-process = "2"
|
||||
tauri-plugin-oauth = "2"
|
||||
|
||||
@@ -87,6 +87,12 @@
|
||||
},
|
||||
{
|
||||
"url": "https://translate.googleapis.com"
|
||||
},
|
||||
{
|
||||
"url": "http://*:*"
|
||||
},
|
||||
{
|
||||
"url": "https://*:*"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ import { getOSPlatform } from '@/utils/misc';
|
||||
type Option = {
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
type SelectProps = {
|
||||
@@ -43,8 +44,8 @@ const StyledSelect: React.FC<SelectProps> = ({
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{options.map(({ value, label }) => (
|
||||
<option key={value} value={value}>
|
||||
{options.map(({ value, label, disabled = false }) => (
|
||||
<option key={value} value={value} disabled={disabled}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
@@ -83,6 +84,12 @@ export const KOSyncSettingsWindow: React.FC = () => {
|
||||
|
||||
// Get the OS name once
|
||||
useEffect(() => {
|
||||
const formatOsName = (name: string): string => {
|
||||
if (!name) return '';
|
||||
if (name.toLowerCase() === 'macos') return 'macOS';
|
||||
return name.charAt(0).toUpperCase() + name.slice(1);
|
||||
};
|
||||
|
||||
const getOsName = async () => {
|
||||
let name = '';
|
||||
if (appService?.appPlatform === 'tauri') {
|
||||
@@ -93,7 +100,7 @@ export const KOSyncSettingsWindow: React.FC = () => {
|
||||
name = platform;
|
||||
}
|
||||
}
|
||||
setOsName(name ? name.charAt(0).toUpperCase() + name.slice(1) : '');
|
||||
setOsName(formatOsName(name));
|
||||
};
|
||||
getOsName();
|
||||
}, [appService]);
|
||||
@@ -236,7 +243,6 @@ export const KOSyncSettingsWindow: React.FC = () => {
|
||||
onClose={() => setIsOpen(false)}
|
||||
title={_('KOReader Sync Settings')}
|
||||
boxClassName='sm:!min-w-[520px] sm:h-auto'
|
||||
bgClassName='!bg-black/60'
|
||||
>
|
||||
<div className='mb-4 mt-0 flex flex-col gap-4 p-2 sm:p-4'>
|
||||
{isConfigured ? (
|
||||
@@ -284,7 +290,7 @@ export const KOSyncSettingsWindow: React.FC = () => {
|
||||
onChange={handleChecksumMethodChange}
|
||||
options={[
|
||||
{ value: 'binary', label: _('File Content (recommended)') },
|
||||
{ value: 'filename', label: _('File Name') },
|
||||
{ value: 'filename', label: _('File Name'), disabled: true },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -335,6 +341,7 @@ export const KOSyncSettingsWindow: React.FC = () => {
|
||||
type='text'
|
||||
placeholder='https://koreader.sync.server'
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
spellCheck='false'
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
@@ -347,6 +354,7 @@ export const KOSyncSettingsWindow: React.FC = () => {
|
||||
type='text'
|
||||
placeholder={_('Your Username')}
|
||||
className='input input-bordered h-12 w-full focus:outline-none focus:ring-0'
|
||||
spellCheck='false'
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -264,7 +264,7 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
onChange={(event) => setCodeLanguage(event.target.value as CodeLanguage)}
|
||||
options={CODE_LANGUAGES.map((lang) => ({
|
||||
value: lang,
|
||||
label: lang,
|
||||
label: lang === 'auto-detect' ? _('Auto') : lang,
|
||||
}))}
|
||||
disabled={!codeHighlighting}
|
||||
/>
|
||||
|
||||
@@ -163,6 +163,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
|
||||
isOpen={true}
|
||||
onClose={handleClose}
|
||||
className='modal-open'
|
||||
bgClassName='sm:!bg-black/20'
|
||||
boxClassName={clsx('sm:min-w-[520px]', appService?.isMobile && 'sm:max-w-[90%] sm:w-3/4')}
|
||||
snapHeight={appService?.isMobile ? 0.7 : undefined}
|
||||
header={
|
||||
|
||||
@@ -59,7 +59,7 @@ export const useProgressSync = (bookKey: string) => {
|
||||
config.xpointer = xpointerResult.xpointer;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to convert CFI to XPointer', error);
|
||||
console.warn('Failed to convert CFI to XPointer', error);
|
||||
}
|
||||
pushConfig(bookKey, config);
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ const Dialog: React.FC<DialogProps> = ({
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'overlay fixed inset-0 z-10 bg-black/50 sm:bg-black/20',
|
||||
'overlay fixed inset-0 z-10 bg-black/50 sm:bg-black/50',
|
||||
appService?.hasRoundedWindow && 'rounded-window',
|
||||
bgClassName,
|
||||
)}
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function Select({
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className={clsx(
|
||||
'select h-8 min-h-8 rounded-md border-none text-end text-sm',
|
||||
'select h-8 min-h-8 rounded-md border-none text-sm',
|
||||
'bg-base-200 focus:outline-none focus:ring-0',
|
||||
className,
|
||||
)}
|
||||
|
||||
@@ -187,7 +187,6 @@ const BookDetailModal: React.FC<BookDetailModalProps> = ({
|
||||
title={editMode ? _('Edit Metadata') : _('Book Details')}
|
||||
isOpen={isOpen}
|
||||
onClose={handleClose}
|
||||
bgClassName='sm:bg-black/50'
|
||||
boxClassName={clsx(
|
||||
editMode ? 'sm:min-w-[600px] sm:max-w-[600px]' : 'sm:min-w-[480px] sm:max-w-[480px]',
|
||||
'sm:h-auto sm:max-h-[90%]',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { md5 } from 'js-md5';
|
||||
import { createHash } from 'crypto';
|
||||
import { randomMd5 } from '@/utils/misc';
|
||||
import { LRUCache } from '@/utils/lru';
|
||||
import { genSSML } from '@/utils/ssml';
|
||||
@@ -6,6 +7,7 @@ import { genSSML } from '@/utils/ssml';
|
||||
const EDGE_SPEECH_URL =
|
||||
'wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1';
|
||||
const EDGE_API_TOKEN = '6A5AA1D4EAFF4E9FB37E23D68491D6F4';
|
||||
const CHROMIUM_FULL_VERSION = '130.0.2849.68';
|
||||
const EDGE_TTS_VOICES = {
|
||||
'af-ZA': ['af-ZA-AdriNeural', 'af-ZA-WillemNeural'],
|
||||
'am-ET': ['am-ET-AmehaNeural', 'am-ET-MekdesNeural'],
|
||||
@@ -120,6 +122,31 @@ const EDGE_TTS_VOICES = {
|
||||
'zh-TW': ['zh-TW-HsiaoChenNeural', 'zh-TW-HsiaoYuNeural', 'zh-TW-YunJheNeural'],
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the Sec-MS-GEC token value.
|
||||
* This function generates a token value based on the current time in Windows file time format
|
||||
* adjusted for clock skew, and rounded down to the nearest 5 minutes. The token is then hashed
|
||||
* using SHA256 and returned as an uppercased hex digest.
|
||||
*
|
||||
* @returns The generated Sec-MS-GEC token value.
|
||||
* @see https://github.com/rany2/edge-tts/issues/290#issuecomment-2464956570
|
||||
*/
|
||||
const WIN_EPOCH_OFFSET = 11644473600; // Windows epoch offset in seconds (1601 to 1970)
|
||||
const S_TO_NS = 1000000000; // Seconds to nanoseconds conversion
|
||||
const generateSecMsGec = () => {
|
||||
let ticks = Math.floor(Date.now() / 1000);
|
||||
// Switch to Windows file time epoch (1601-01-01 00:00:00 UTC)
|
||||
ticks += WIN_EPOCH_OFFSET;
|
||||
// Round down to the nearest 5 minutes (300 seconds)
|
||||
ticks -= ticks % 300;
|
||||
// Convert the ticks to 100-nanosecond intervals (Windows file time format)
|
||||
ticks *= S_TO_NS / 100;
|
||||
// Create the string to hash by concatenating the ticks and the trusted client token
|
||||
const strToHash = `${ticks.toFixed(0)}${EDGE_API_TOKEN}`;
|
||||
// Compute the SHA256 hash and return the uppercased hex digest
|
||||
return createHash('sha256').update(strToHash, 'ascii').digest('hex').toUpperCase();
|
||||
};
|
||||
|
||||
const genVoiceList = (voices: Record<string, string[]>) => {
|
||||
return Object.entries(voices).flatMap(([lang, voices]) => {
|
||||
return voices.map((id) => {
|
||||
@@ -150,7 +177,13 @@ export class EdgeSpeechTTS {
|
||||
|
||||
async #fetchEdgeSpeechWs({ lang, text, voice, rate }: EdgeTTSPayload): Promise<Response> {
|
||||
const connectId = randomMd5();
|
||||
const url = `${EDGE_SPEECH_URL}?ConnectionId=${connectId}&TrustedClientToken=${EDGE_API_TOKEN}`;
|
||||
const params = new URLSearchParams({
|
||||
ConnectionId: connectId,
|
||||
TrustedClientToken: EDGE_API_TOKEN,
|
||||
'Sec-MS-GEC': generateSecMsGec(),
|
||||
'Sec-MS-GEC-Version': `1-${CHROMIUM_FULL_VERSION}`,
|
||||
});
|
||||
const url = `${EDGE_SPEECH_URL}?${params.toString()}`;
|
||||
const date = new Date().toString();
|
||||
const configHeaders = {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { md5 } from 'js-md5';
|
||||
import { Book } from '@/types/book';
|
||||
import { KoreaderSyncChecksumMethod } from '@/types/settings';
|
||||
import { getAPIBaseUrl } from '../environment';
|
||||
import { fetch as tauriFetch } from '@tauri-apps/plugin-http';
|
||||
import { KoSyncProxyPayload } from '@/types/kosync';
|
||||
import { isLanAddress } from '@/utils/network';
|
||||
import { getAPIBaseUrl, isTauriAppPlatform } from '../environment';
|
||||
|
||||
/**
|
||||
* Interface for KOSync progress response from the server
|
||||
@@ -23,6 +25,7 @@ export class KOSyncClient {
|
||||
private checksumMethod: KoreaderSyncChecksumMethod;
|
||||
private deviceId: string;
|
||||
private deviceName: string;
|
||||
private isLanServer: boolean;
|
||||
|
||||
constructor(
|
||||
serverUrl: string,
|
||||
@@ -38,6 +41,7 @@ export class KOSyncClient {
|
||||
this.checksumMethod = checksumMethod;
|
||||
this.deviceId = deviceId;
|
||||
this.deviceName = deviceName;
|
||||
this.isLanServer = isLanAddress(this.serverUrl);
|
||||
}
|
||||
|
||||
private async request(
|
||||
@@ -57,6 +61,24 @@ export class KOSyncClient {
|
||||
headers.set('X-Auth-Key', this.userkey);
|
||||
}
|
||||
|
||||
if (this.isLanServer) {
|
||||
const fetch = isTauriAppPlatform() ? tauriFetch : window.fetch;
|
||||
const directUrl = `${this.serverUrl}${endpoint}`;
|
||||
|
||||
return fetch(directUrl, {
|
||||
method,
|
||||
headers: {
|
||||
accept: 'application/vnd.koreader.v1+json',
|
||||
...Object.fromEntries(headers.entries()),
|
||||
},
|
||||
body,
|
||||
danger: {
|
||||
acceptInvalidCerts: true,
|
||||
acceptInvalidHostnames: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const proxyUrl = `${getAPIBaseUrl()}/kosync`;
|
||||
|
||||
const proxyBody: KoSyncProxyPayload = {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
export const isLanAddress = (url: string) => {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
const hostname = urlObj.hostname;
|
||||
|
||||
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for IPv4 private ranges
|
||||
const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
||||
const match = hostname.match(ipv4Regex);
|
||||
|
||||
if (match) {
|
||||
const [, a, b, c, d] = match.map(Number);
|
||||
if (a === undefined || b === undefined || c === undefined || d === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate IP format
|
||||
if (a > 255 || b > 255 || c > 255 || d > 255) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check private IP ranges:
|
||||
// 10.0.0.0/8 (10.0.0.0 to 10.255.255.255)
|
||||
if (a === 10) return true;
|
||||
|
||||
// 172.16.0.0/12 (172.16.0.0 to 172.31.255.255)
|
||||
if (a === 172 && b >= 16 && b <= 31) return true;
|
||||
|
||||
// 192.168.0.0/16 (192.168.0.0 to 192.168.255.255)
|
||||
if (a === 192 && b === 168) return true;
|
||||
|
||||
// 169.254.0.0/16 (link-local addresses)
|
||||
if (a === 169 && b === 254) return true;
|
||||
}
|
||||
|
||||
// Check for IPv6 private addresses (simplified check)
|
||||
if (hostname.includes(':')) {
|
||||
if (
|
||||
hostname.startsWith('::1') ||
|
||||
hostname.startsWith('fe80:') ||
|
||||
hostname.startsWith('fc00:') ||
|
||||
hostname.startsWith('fd00:')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -131,9 +131,6 @@ const getColorStyles = (
|
||||
${overrideColor ? `color: ${primary};` : isDarkMode ? `color: lightblue;` : ''}
|
||||
text-decoration: none;
|
||||
}
|
||||
p:has(img), span:has(img) {
|
||||
background-color: ${bg};
|
||||
}
|
||||
body.pbg {
|
||||
${isDarkMode ? `background-color: ${bg} !important;` : ''}
|
||||
}
|
||||
@@ -142,7 +139,7 @@ const getColorStyles = (
|
||||
${!isDarkMode && overrideColor ? 'mix-blend-mode: multiply;' : ''}
|
||||
}
|
||||
/* horizontal rule */
|
||||
*:has(hr) {
|
||||
*:has(> hr[class]) {
|
||||
background-color: ${bg};
|
||||
}
|
||||
hr {
|
||||
@@ -324,7 +321,7 @@ const getLayoutStyles = (
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
.duokan-footnote img {
|
||||
.duokan-footnote img:not([class]) {
|
||||
width: 0.8em;
|
||||
height: 0.8em;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ function ReadestSync:tryRefreshToken()
|
||||
self.settings.refresh_token = response.refresh_token
|
||||
self.settings.expires_at = response.expires_at
|
||||
self.settings.expires_in = response.expires_in
|
||||
G_reader_settings:writeSetting("readest_sync", self.settings)
|
||||
G_reader_settings:saveSetting("readest_sync", self.settings)
|
||||
else
|
||||
logger.err("ReadestSync: Token refresh failed:", response or "Unknown error")
|
||||
end
|
||||
@@ -229,8 +229,8 @@ function ReadestSync:doLogin(email, password, menu)
|
||||
self.settings.refresh_token = response.refresh_token
|
||||
self.settings.expires_at = response.expires_at
|
||||
self.settings.expires_in = response.expires_in
|
||||
G_reader_settings:writeSetting("readest_sync", self.settings)
|
||||
|
||||
G_reader_settings:saveSetting("readest_sync", self.settings)
|
||||
|
||||
if menu then
|
||||
menu:updateItems()
|
||||
end
|
||||
@@ -261,7 +261,7 @@ function ReadestSync:logout(menu)
|
||||
self.settings.refresh_token = nil
|
||||
self.settings.expires_at = nil
|
||||
self.settings.expires_in = nil
|
||||
G_reader_settings:writeSetting("readest_sync", self.settings)
|
||||
G_reader_settings:saveSetting("readest_sync", self.settings)
|
||||
|
||||
if menu then
|
||||
menu:updateItems()
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"base_url": "http://web.readest.com/api",
|
||||
"base_url": "https://web.readest.com/api",
|
||||
"name": "readest-sync-api",
|
||||
"methods": {
|
||||
"pullChanges": {
|
||||
"path": "/sync",
|
||||
"method": "GET",
|
||||
"required_params": ["since", "type", "book"],
|
||||
"expected_status": [200, 400, 401, 403]
|
||||
"expected_status": [200, 400, 301, 401, 403]
|
||||
},
|
||||
"pushChanges": {
|
||||
"path": "/sync",
|
||||
"method": "POST",
|
||||
"required_params": ["books", "notes", "configs"],
|
||||
"payload": ["books", "notes", "configs"],
|
||||
"expected_status": [200, 201, 400, 401, 403]
|
||||
"expected_status": [200, 201, 301, 400, 401, 403]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
Submodule packages/foliate-js updated: 655100371f...db24aa01cb
Reference in New Issue
Block a user