chore: experimental vinext build (#3486)
This commit is contained in:
@@ -70,11 +70,6 @@ jobs:
|
||||
run: |
|
||||
pnpm format:check || (pnpm format && git diff && exit 1)
|
||||
|
||||
- name: run tests
|
||||
working-directory: apps/readest-app
|
||||
run: |
|
||||
pnpm test -- --watch=false
|
||||
|
||||
- name: install playwright browsers
|
||||
if: matrix.config.platform == 'web'
|
||||
working-directory: apps/readest-app
|
||||
@@ -91,6 +86,11 @@ jobs:
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- if: matrix.config.platform == 'tauri'
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.config.platform }}-cargo
|
||||
|
||||
- name: install system dependencies (tauri)
|
||||
if: matrix.config.platform == 'tauri'
|
||||
run: |
|
||||
@@ -103,18 +103,13 @@ jobs:
|
||||
run: xvfb-run pnpm test:pr:tauri
|
||||
|
||||
- name: run lint
|
||||
if: matrix.config.platform == 'web'
|
||||
working-directory: apps/readest-app
|
||||
run: |
|
||||
pnpm lint
|
||||
|
||||
- name: build the web App
|
||||
- name: build the web app
|
||||
if: matrix.config.platform == 'web'
|
||||
working-directory: apps/readest-app
|
||||
run: |
|
||||
pnpm build-web && pnpm check:all
|
||||
|
||||
- name: build the Tauri App
|
||||
if: matrix.config.platform == 'tauri'
|
||||
working-directory: apps/readest-app
|
||||
run: |
|
||||
pnpm build && pnpm check:all
|
||||
|
||||
@@ -60,3 +60,4 @@ src-tauri/gen
|
||||
/public/fallback-*.js
|
||||
/public/swe-worker-*.js
|
||||
|
||||
/dist/
|
||||
|
||||
@@ -11,6 +11,7 @@ const eslintConfig = defineConfig([
|
||||
{
|
||||
rules: {
|
||||
...jsxA11y.configs.recommended.rules,
|
||||
'@next/next/no-img-element': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
@@ -25,10 +26,14 @@ const eslintConfig = defineConfig([
|
||||
'node_modules/**',
|
||||
'.next/**',
|
||||
'.open-next/**',
|
||||
'.wrangler/**',
|
||||
'dist/**',
|
||||
'out/**',
|
||||
'build/**',
|
||||
'public/**',
|
||||
'src-tauri/**',
|
||||
'next-env.d.ts',
|
||||
'i18next-scanner.config.cjs',
|
||||
]),
|
||||
]);
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
const options = {
|
||||
debug: false,
|
||||
sort: false,
|
||||
func: {
|
||||
list: ['_'],
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
lngs: [
|
||||
'de',
|
||||
'ja',
|
||||
'es',
|
||||
'fa',
|
||||
'fr',
|
||||
'it',
|
||||
'el',
|
||||
'ko',
|
||||
'uk',
|
||||
'nl',
|
||||
'sl',
|
||||
'sv',
|
||||
'pl',
|
||||
'pt',
|
||||
'ru',
|
||||
'tr',
|
||||
'hi',
|
||||
'id',
|
||||
'vi',
|
||||
'ms',
|
||||
'he',
|
||||
'ar',
|
||||
'th',
|
||||
'bo',
|
||||
'bn',
|
||||
'ta',
|
||||
'si',
|
||||
'zh-CN',
|
||||
'zh-TW',
|
||||
],
|
||||
ns: ['translation'],
|
||||
defaultNs: 'translation',
|
||||
defaultValue: '__STRING_NOT_TRANSLATED__',
|
||||
resource: {
|
||||
loadPath: './public/locales/{{lng}}/{{ns}}.json',
|
||||
savePath: './public/locales/{{lng}}/{{ns}}.json',
|
||||
jsonIndent: 2,
|
||||
lineEnding: '\n',
|
||||
},
|
||||
keySeparator: false,
|
||||
nsSeparator: false,
|
||||
interpolation: {
|
||||
prefix: '{{',
|
||||
suffix: '}}',
|
||||
},
|
||||
metadata: {},
|
||||
allowDynamicKeys: true,
|
||||
removeUnusedKeys: true,
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
input: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.test.{js,jsx,ts,tsx}'],
|
||||
output: '.',
|
||||
options,
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
module.exports = {
|
||||
input: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.test.{js,jsx,ts,tsx}'],
|
||||
output: '.',
|
||||
options: {
|
||||
debug: false,
|
||||
sort: false,
|
||||
func: {
|
||||
list: ['_'],
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
lngs: [
|
||||
'de',
|
||||
'ja',
|
||||
'es',
|
||||
'fa',
|
||||
'fr',
|
||||
'it',
|
||||
'el',
|
||||
'ko',
|
||||
'uk',
|
||||
'nl',
|
||||
'sl',
|
||||
'sv',
|
||||
'pl',
|
||||
'pt',
|
||||
'ru',
|
||||
'tr',
|
||||
'hi',
|
||||
'id',
|
||||
'vi',
|
||||
'ms',
|
||||
'he',
|
||||
'ar',
|
||||
'th',
|
||||
'bo',
|
||||
'bn',
|
||||
'ta',
|
||||
'si',
|
||||
'zh-CN',
|
||||
'zh-TW',
|
||||
],
|
||||
ns: ['translation'],
|
||||
defaultNs: 'translation',
|
||||
defaultValue: '__STRING_NOT_TRANSLATED__',
|
||||
resource: {
|
||||
loadPath: './public/locales/{{lng}}/{{ns}}.json',
|
||||
savePath: './public/locales/{{lng}}/{{ns}}.json',
|
||||
jsonIndent: 2,
|
||||
lineEnding: '\n',
|
||||
},
|
||||
keySeparator: false,
|
||||
nsSeparator: false,
|
||||
interpolation: {
|
||||
prefix: '{{',
|
||||
suffix: '}}',
|
||||
},
|
||||
metadata: {},
|
||||
allowDynamicKeys: true,
|
||||
removeUnusedKeys: true,
|
||||
},
|
||||
};
|
||||
@@ -59,6 +59,14 @@ const nextConfig = {
|
||||
'marked',
|
||||
]),
|
||||
],
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/reader/:ids',
|
||||
destination: '/reader?ids=:ids',
|
||||
},
|
||||
];
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "@readest/readest-app",
|
||||
"version": "0.9.101",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "dotenv -e .env.tauri -- next dev",
|
||||
"build": "dotenv -e .env.tauri -- next build",
|
||||
@@ -9,8 +10,11 @@
|
||||
"dev-web": "dotenv -e .env.web -- next dev",
|
||||
"build-web": "dotenv -e .env.web -- next build",
|
||||
"start-web": "dotenv -e .env.web -- next start",
|
||||
"dev-web:vinext": "dotenv -e .env.web -- vinext dev --port 3000",
|
||||
"build-web:vinext": "dotenv -e .env.web -- vinext build",
|
||||
"start-web:vinext": "dotenv -e .env.web -- vinext start",
|
||||
"build-tauri": "dotenv -e .env.tauri -- next build",
|
||||
"i18n:extract": "i18next-scanner",
|
||||
"i18n:extract": "i18next-scanner --config i18next-scanner.config.cjs",
|
||||
"lint": "eslint .",
|
||||
"test": "dotenv -e .env -e .env.test.local vitest",
|
||||
"test:browser": "vitest --config vitest.browser.config.mts --watch=false",
|
||||
@@ -146,9 +150,9 @@
|
||||
"overlayscrollbars": "^2.11.4",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"posthog-js": "^1.246.0",
|
||||
"react": "19.2.0",
|
||||
"react": "19.2.4",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "19.2.0",
|
||||
"react-dom": "19.2.4",
|
||||
"react-i18next": "^15.2.0",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-responsive": "^10.0.0",
|
||||
@@ -191,6 +195,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"@vitejs/plugin-rsc": "^0.5.21",
|
||||
"@vitest/browser-playwright": "^4.0.18",
|
||||
"@vitest/browser-webdriverio": "^4.0.18",
|
||||
"@wdio/cli": "^9.24.0",
|
||||
@@ -216,9 +221,12 @@
|
||||
"postcss-cli": "^11.0.0",
|
||||
"postcss-nested": "^7.0.2",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react-server-dom-webpack": "^19.2.4",
|
||||
"serwist": "^9.3.0",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"typescript": "^5.7.2",
|
||||
"vinext": "^0.0.21",
|
||||
"vite": "^7.3.1",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^4.0.18",
|
||||
"wrangler": "^4.60.0"
|
||||
|
||||
Submodule apps/readest-app/src-tauri/plugins/tauri-plugin-turso updated: bb12a796eb...b18e78107a
@@ -1,6 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { IoChevronBack, IoChevronForward } from 'react-icons/io5';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { Insets } from '@/types/misc';
|
||||
@@ -439,7 +438,8 @@ const ImageViewer: React.FC<ImageViewerProps> = ({
|
||||
className={clsx('relative flex h-full w-full items-center justify-center overflow-hidden')}
|
||||
onClick={handleContainerClick}
|
||||
>
|
||||
<Image
|
||||
<img
|
||||
role='none'
|
||||
src={decodeURIComponent(src)}
|
||||
ref={imageRef}
|
||||
alt={_('Zoomed')}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { MdCheck } from 'react-icons/md';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -144,7 +143,7 @@ const BookMenu: React.FC<BookMenuProps> = ({ menuClassName, setIsDropdownOpen })
|
||||
<MenuItem
|
||||
key={book.hash}
|
||||
Icon={
|
||||
<Image
|
||||
<img
|
||||
src={book.coverImageUrl!}
|
||||
alt={book.title}
|
||||
width={56}
|
||||
|
||||
@@ -20,7 +20,7 @@ const useBooksManager = () => {
|
||||
useEffect(() => {
|
||||
if (shouldUpdateSearchParams) {
|
||||
const ids = bookKeys.map((key) => key.split('-')[0]!);
|
||||
if (ids) {
|
||||
if (ids.length > 0) {
|
||||
navigateToReader(router, ids, searchParams?.toString() || '', { scroll: false });
|
||||
}
|
||||
setShouldUpdateSearchParams(false);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import clsx from 'clsx';
|
||||
import Image from 'next/image';
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
import { Book } from '@/types/book';
|
||||
import { LibraryCoverFitType, LibraryViewModeType } from '@/types/settings';
|
||||
@@ -70,10 +69,9 @@ const BookCover: React.FC<BookCoverProps> = memo<BookCoverProps>(
|
||||
>
|
||||
{coverFit === 'crop' ? (
|
||||
<>
|
||||
<Image
|
||||
<img
|
||||
src={book.metadata?.coverImageUrl || book.coverImageUrl!}
|
||||
alt={book.title}
|
||||
fill={true}
|
||||
className={clsx('cover-image crop-cover-img object-cover', imageClassName)}
|
||||
onLoad={handleImageLoad}
|
||||
onError={handleImageError}
|
||||
@@ -90,7 +88,7 @@ const BookCover: React.FC<BookCoverProps> = memo<BookCoverProps>(
|
||||
mode === 'grid' ? 'items-end' : 'items-center',
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
<img
|
||||
src={book.metadata?.coverImageUrl || book.coverImageUrl!}
|
||||
alt={book.title}
|
||||
width={0}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { useState, useEffect, memo } from 'react';
|
||||
|
||||
interface CachedImageProps {
|
||||
@@ -120,11 +119,11 @@ const CachedImageComponent = ({
|
||||
}
|
||||
|
||||
if (fill) {
|
||||
return <Image src={cachedUrl} alt={alt} fill className={className} sizes={sizes} />;
|
||||
return <img src={cachedUrl} alt={alt} className={className} sizes={sizes} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Image
|
||||
<img
|
||||
src={cachedUrl}
|
||||
alt={alt}
|
||||
width={width}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import clsx from 'clsx';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { IconType } from 'react-icons';
|
||||
|
||||
@@ -68,7 +67,7 @@ const UserAvatar: React.FC<UserAvatarProps> = ({
|
||||
>
|
||||
{url ? (
|
||||
<div>
|
||||
<Image
|
||||
<img
|
||||
src={cachedImageUrl || url}
|
||||
alt='User Avatar'
|
||||
className={clsx('rounded-full', className, borderClassName)}
|
||||
|
||||
@@ -1,7 +1,39 @@
|
||||
import i18n from 'i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import { options } from '../../i18next-scanner.config';
|
||||
// Keep in sync with i18next-scanner.config.cjs
|
||||
const SUPPORTED_LNGS = [
|
||||
'en',
|
||||
'de',
|
||||
'ja',
|
||||
'es',
|
||||
'fa',
|
||||
'fr',
|
||||
'it',
|
||||
'el',
|
||||
'ko',
|
||||
'uk',
|
||||
'nl',
|
||||
'sl',
|
||||
'sv',
|
||||
'pl',
|
||||
'pt',
|
||||
'ru',
|
||||
'tr',
|
||||
'hi',
|
||||
'id',
|
||||
'vi',
|
||||
'ms',
|
||||
'he',
|
||||
'ar',
|
||||
'th',
|
||||
'bo',
|
||||
'bn',
|
||||
'ta',
|
||||
'si',
|
||||
'zh-CN',
|
||||
'zh-TW',
|
||||
];
|
||||
|
||||
const isBrowser = typeof window !== 'undefined';
|
||||
|
||||
@@ -15,7 +47,7 @@ const initI18n = async () => {
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
supportedLngs: ['en', ...options.lngs],
|
||||
supportedLngs: SUPPORTED_LNGS,
|
||||
fallbackLng: {
|
||||
'zh-HK': ['zh-TW', 'en'],
|
||||
kk: ['ru', 'en'],
|
||||
@@ -26,8 +58,8 @@ const initI18n = async () => {
|
||||
tt: ['ru', 'en'],
|
||||
default: ['en'],
|
||||
},
|
||||
ns: options.ns,
|
||||
defaultNS: options.defaultNs,
|
||||
ns: ['translation'],
|
||||
defaultNS: 'translation',
|
||||
...(isBrowser && {
|
||||
backend: {
|
||||
loadPath: '/locales/{{lng}}/{{ns}}.json',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { md5 } from 'js-md5';
|
||||
import WebSocket from 'isomorphic-ws';
|
||||
import { createHash } from 'crypto';
|
||||
import { randomMd5 } from '@/utils/misc';
|
||||
import { LRUCache } from '@/utils/lru';
|
||||
import { genSSML } from '@/utils/ssml';
|
||||
@@ -205,7 +204,7 @@ const EDGE_TTS_VOICES = {
|
||||
*/
|
||||
const WIN_EPOCH_OFFSET = 11644473600; // Windows epoch offset in seconds (1601 to 1970)
|
||||
const S_TO_NS = 1000000000; // Seconds to nanoseconds conversion
|
||||
const generateSecMsGec = () => {
|
||||
const generateSecMsGec = async () => {
|
||||
let ticks = Math.floor(Date.now() / 1000);
|
||||
// Switch to Windows file time epoch (1601-01-01 00:00:00 UTC)
|
||||
ticks += WIN_EPOCH_OFFSET;
|
||||
@@ -216,7 +215,14 @@ const generateSecMsGec = () => {
|
||||
// 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 encoder = new TextEncoder();
|
||||
const data = encoder.encode(strToHash);
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
return hashArray
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
.toUpperCase();
|
||||
};
|
||||
|
||||
const generateMuid = () => {
|
||||
@@ -298,7 +304,7 @@ export class EdgeSpeechTTS {
|
||||
const params = new URLSearchParams({
|
||||
ConnectionId: connectId,
|
||||
TrustedClientToken: EDGE_API_TOKEN,
|
||||
'Sec-MS-GEC': generateSecMsGec(),
|
||||
'Sec-MS-GEC': await generateSecMsGec(),
|
||||
'Sec-MS-GEC-Version': `1-${CHROMIUM_FULL_VERSION}`,
|
||||
});
|
||||
const url = `${EDGE_SPEECH_URL}?${params.toString()}`;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import path from 'node:path';
|
||||
import vinext from 'vinext';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vinext()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@pdfjs': path.resolve('public/vendor/pdfjs'),
|
||||
'@simplecc': path.resolve('public/vendor/simplecc'),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
onwarn(warning, defaultHandler) {
|
||||
if (warning.message?.includes("Can't resolve original location of error")) return;
|
||||
defaultHandler(warning);
|
||||
},
|
||||
},
|
||||
},
|
||||
ssr: {
|
||||
noExternal: ['tinycolor2'],
|
||||
},
|
||||
});
|
||||
Generated
+796
-426
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user