Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5b3ee8709 | ||
|
|
4f1e01dde0 | ||
|
|
d42ff51de5 | ||
|
|
c39861b7b7 | ||
|
|
d39b9fd73e | ||
|
|
ecab4ab634 | ||
|
|
5e67e15842 | ||
|
|
2af1a8b72c | ||
|
|
2510ed0ebb | ||
|
|
6827985289 | ||
|
|
a095a2c01c | ||
|
|
2033ff6777 | ||
|
|
0c22288833 | ||
|
|
0576150067 | ||
|
|
9cdcf616f7 | ||
|
|
2de10364f3 |
2
.env
2
.env
@@ -2,7 +2,7 @@ GENERATE_SOURCEMAP=false
|
|||||||
|
|
||||||
REACT_APP_NAME=KISS Translator
|
REACT_APP_NAME=KISS Translator
|
||||||
REACT_APP_NAME_CN=简约翻译
|
REACT_APP_NAME_CN=简约翻译
|
||||||
REACT_APP_VERSION=2.0.0
|
REACT_APP_VERSION=2.0.1
|
||||||
|
|
||||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ A simple, open source [bilingual translation extension & Greasemonkey script](ht
|
|||||||
|
|
||||||
> Note: For the following reasons, it is recommended to use browser extensions first
|
> Note: For the following reasons, it is recommended to use browser extensions first
|
||||||
>
|
>
|
||||||
> - Browser extensions have more complete functions (subtitle translation, local language recognition, context menu, etc.)
|
> - Browser extensions have more complete functions (local language recognition, context menu, etc.)
|
||||||
> - Grease Monkey script will encounter more usage problems (cross domain issues, script conflicts, etc.)
|
> - Grease Monkey script will encounter more usage problems (cross domain issues, script conflicts, etc.)
|
||||||
|
|
||||||
- [x] Browser extension
|
- [x] Browser extension
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
|
|
||||||
> 注:基于以下原因,建议优先使用浏览器扩展
|
> 注:基于以下原因,建议优先使用浏览器扩展
|
||||||
>
|
>
|
||||||
> - 浏览器扩展的功能更完整(字幕翻译、本地语言识别、右键菜单等)
|
> - 浏览器扩展的功能更完整(本地语言识别、右键菜单等)
|
||||||
> - 油猴脚本会遇到更多使用上的问题(跨域问题、脚本冲突等)
|
> - 油猴脚本会遇到更多使用上的问题(跨域问题、脚本冲突等)
|
||||||
|
|
||||||
- [x] 浏览器扩展
|
- [x] 浏览器扩展
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ const userscriptWebpack = (config, env) => {
|
|||||||
// @connect openai.azure.com
|
// @connect openai.azure.com
|
||||||
// @connect workers.dev
|
// @connect workers.dev
|
||||||
// @connect github.io
|
// @connect github.io
|
||||||
|
// @connect github.com
|
||||||
// @connect githubusercontent.com
|
// @connect githubusercontent.com
|
||||||
// @connect kiss-translator.rayjar.com
|
// @connect kiss-translator.rayjar.com
|
||||||
// @connect ghproxy.com
|
// @connect ghproxy.com
|
||||||
@@ -130,7 +131,6 @@ const userscriptWebpack = (config, env) => {
|
|||||||
config.entry = {
|
config.entry = {
|
||||||
main: paths.appIndexJs,
|
main: paths.appIndexJs,
|
||||||
options: paths.appSrc + "/options.js",
|
options: paths.appSrc + "/options.js",
|
||||||
injector: paths.appSrc + "/injector.js",
|
|
||||||
"kiss-translator.user": paths.appSrc + "/userscript.js",
|
"kiss-translator.user": paths.appSrc + "/userscript.js",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "kiss-translator",
|
"name": "kiss-translator",
|
||||||
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "__MSG_app_name__",
|
"name": "__MSG_app_name__",
|
||||||
"description": "__MSG_app_description__",
|
"description": "__MSG_app_description__",
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "__MSG_app_name__",
|
"name": "__MSG_app_name__",
|
||||||
"description": "__MSG_app_description__",
|
"description": "__MSG_app_description__",
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "__MSG_app_name__",
|
"name": "__MSG_app_name__",
|
||||||
"description": "__MSG_app_description__",
|
"description": "__MSG_app_description__",
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
CMD_OPEN_TRANBOX,
|
CMD_OPEN_TRANBOX,
|
||||||
CLIENT_THUNDERBIRD,
|
CLIENT_THUNDERBIRD,
|
||||||
MSG_SET_LOGLEVEL,
|
MSG_SET_LOGLEVEL,
|
||||||
|
MSG_CLEAR_CACHES,
|
||||||
} from "./config";
|
} from "./config";
|
||||||
import { getSettingWithDefault, tryInitDefaultData } from "./libs/storage";
|
import { getSettingWithDefault, tryInitDefaultData } from "./libs/storage";
|
||||||
import { trySyncSettingAndRules } from "./libs/sync";
|
import { trySyncSettingAndRules } from "./libs/sync";
|
||||||
@@ -275,6 +276,7 @@ const messageHandlers = {
|
|||||||
[MSG_BUILTINAI_DETECT]: (args) => chromeDetect(args),
|
[MSG_BUILTINAI_DETECT]: (args) => chromeDetect(args),
|
||||||
[MSG_BUILTINAI_TRANSLATE]: (args) => chromeTranslate(args),
|
[MSG_BUILTINAI_TRANSLATE]: (args) => chromeTranslate(args),
|
||||||
[MSG_SET_LOGLEVEL]: (args) => logger.setLevel(args),
|
[MSG_SET_LOGLEVEL]: (args) => logger.setLevel(args),
|
||||||
|
[MSG_CLEAR_CACHES]: () => tryClearCaches(),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { trySyncAllSubRules } from "./libs/subRules";
|
|||||||
import { isInBlacklist } from "./libs/blacklist";
|
import { isInBlacklist } from "./libs/blacklist";
|
||||||
import { runSubtitle } from "./subtitle/subtitle";
|
import { runSubtitle } from "./subtitle/subtitle";
|
||||||
import { logger } from "./libs/log";
|
import { logger } from "./libs/log";
|
||||||
|
import { injectInlineJs } from "./libs/injector";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 油猴脚本设置页面
|
* 油猴脚本设置页面
|
||||||
@@ -35,9 +36,10 @@ function runSettingPage() {
|
|||||||
const ping = genEventName();
|
const ping = genEventName();
|
||||||
window.addEventListener(ping, handlePing);
|
window.addEventListener(ping, handlePing);
|
||||||
// window.eval(`(${injectScript})("${ping}")`); // eslint-disable-line
|
// window.eval(`(${injectScript})("${ping}")`); // eslint-disable-line
|
||||||
const script = document.createElement("script");
|
injectInlineJs(
|
||||||
script.textContent = `(${injectScript})("${ping}")`;
|
`(${injectScript})("${ping}")`,
|
||||||
document.head.append(script);
|
"kiss-translator-options-injector"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +129,7 @@ function showErr(message) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const closeButton = document.createElement("span");
|
const closeButton = document.createElement("span");
|
||||||
closeButton.innerHTML = "×";
|
closeButton.textContent = "×";
|
||||||
|
|
||||||
Object.assign(closeButton.style, {
|
Object.assign(closeButton.style, {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
@@ -216,7 +218,7 @@ export async function run(isUserscript = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 字幕翻译
|
// 字幕翻译
|
||||||
runSubtitle({ href, setting, rule });
|
runSubtitle({ href, setting, rule, isUserscript });
|
||||||
|
|
||||||
// 监听消息
|
// 监听消息
|
||||||
// !isUserscript && runtimeListener(translator);
|
// !isUserscript && runtimeListener(translator);
|
||||||
|
|||||||
@@ -448,7 +448,7 @@ const defaultApi = {
|
|||||||
useBatchFetch: false, // 是否启用聚合发送请求
|
useBatchFetch: false, // 是否启用聚合发送请求
|
||||||
useContext: false, // 是否启用智能上下文
|
useContext: false, // 是否启用智能上下文
|
||||||
contextSize: DEFAULT_CONTEXT_SIZE, // 智能上下文保留会话数
|
contextSize: DEFAULT_CONTEXT_SIZE, // 智能上下文保留会话数
|
||||||
temperature: 0,
|
temperature: 0.0,
|
||||||
maxTokens: 20480,
|
maxTokens: 20480,
|
||||||
think: false,
|
think: false,
|
||||||
thinkIgnore: "qwen3,deepseek-r1",
|
thinkIgnore: "qwen3,deepseek-r1",
|
||||||
|
|||||||
@@ -1609,8 +1609,8 @@ export const I18N = {
|
|||||||
zh_TW: `AI处理切割长度(200-20000)`,
|
zh_TW: `AI处理切割长度(200-20000)`,
|
||||||
},
|
},
|
||||||
subtitle_helper_1: {
|
subtitle_helper_1: {
|
||||||
zh: `1、目前仅支持Youtube桌面网站,且仅支持浏览器扩展。`,
|
zh: `1、目前仅支持Youtube桌面网站。`,
|
||||||
en: `1. Currently only supports Youtube desktop website and browser extension.`,
|
en: `1. Currently only supports Youtube desktop website.`,
|
||||||
zh_TW: `1.目前僅支援Youtube桌面網站,且僅支援瀏覽器擴充功能。`,
|
zh_TW: `1.目前僅支援Youtube桌面網站,且僅支援瀏覽器擴充功能。`,
|
||||||
},
|
},
|
||||||
subtitle_helper_2: {
|
subtitle_helper_2: {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export const CMD_TOGGLE_STYLE = "toggleStyle";
|
|||||||
export const CMD_OPEN_OPTIONS = "openOptions";
|
export const CMD_OPEN_OPTIONS = "openOptions";
|
||||||
export const CMD_OPEN_TRANBOX = "openTranbox";
|
export const CMD_OPEN_TRANBOX = "openTranbox";
|
||||||
|
|
||||||
export const MSG_FETCH = "fetch";
|
export const MSG_FETCH = "kiss_fetch";
|
||||||
export const MSG_GET_HTTPCACHE = "get_httpcache";
|
export const MSG_GET_HTTPCACHE = "get_httpcache";
|
||||||
export const MSG_PUT_HTTPCACHE = "put_httpcache";
|
export const MSG_PUT_HTTPCACHE = "put_httpcache";
|
||||||
export const MSG_OPEN_OPTIONS = "open_options";
|
export const MSG_OPEN_OPTIONS = "open_options";
|
||||||
@@ -25,6 +25,7 @@ export const MSG_UPDATE_CSP = "update_csp";
|
|||||||
export const MSG_BUILTINAI_DETECT = "builtinai_detect";
|
export const MSG_BUILTINAI_DETECT = "builtinai_detect";
|
||||||
export const MSG_BUILTINAI_TRANSLATE = "builtinai_translte";
|
export const MSG_BUILTINAI_TRANSLATE = "builtinai_translte";
|
||||||
export const MSG_SET_LOGLEVEL = "set_loglevel";
|
export const MSG_SET_LOGLEVEL = "set_loglevel";
|
||||||
|
export const MSG_CLEAR_CACHES = "clear_caches";
|
||||||
|
|
||||||
export const MSG_XHR_DATA_YOUTUBE = "KISS_XHR_DATA_YOUTUBE";
|
export const MSG_XHR_DATA_YOUTUBE = "KISS_XHR_DATA_YOUTUBE";
|
||||||
// export const MSG_GLOBAL_VAR_FETCH = "KISS_GLOBAL_VAR_FETCH";
|
// export const MSG_GLOBAL_VAR_FETCH = "KISS_GLOBAL_VAR_FETCH";
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export const DEFAULT_MOUSE_HOVER_SETTING = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_SETTING = {
|
export const DEFAULT_SETTING = {
|
||||||
darkMode: false, // 深色模式
|
darkMode: "auto", // 深色模式
|
||||||
uiLang: "en", // 界面语言
|
uiLang: "en", // 界面语言
|
||||||
// fetchLimit: DEFAULT_FETCH_LIMIT, // 最大任务数量(移至rule,作废)
|
// fetchLimit: DEFAULT_FETCH_LIMIT, // 最大任务数量(移至rule,作废)
|
||||||
// fetchInterval: DEFAULT_FETCH_INTERVAL, // 任务间隔时间(移至rule,作废)
|
// fetchInterval: DEFAULT_FETCH_INTERVAL, // 任务间隔时间(移至rule,作废)
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ export function useDarkMode() {
|
|||||||
} = useSetting();
|
} = useSetting();
|
||||||
|
|
||||||
const toggleDarkMode = useCallback(() => {
|
const toggleDarkMode = useCallback(() => {
|
||||||
updateSetting({ darkMode: !darkMode });
|
const nextMode = {
|
||||||
|
light: "dark",
|
||||||
|
dark: "auto",
|
||||||
|
auto: "light",
|
||||||
|
};
|
||||||
|
updateSetting({ darkMode: nextMode[darkMode] || "light" });
|
||||||
}, [darkMode, updateSetting]);
|
}, [darkMode, updateSetting]);
|
||||||
|
|
||||||
return { darkMode, toggleDarkMode };
|
return { darkMode, toggleDarkMode };
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { debounceSyncMeta } from "../libs/storage";
|
|||||||
import Loading from "./Loading";
|
import Loading from "./Loading";
|
||||||
import { logger } from "../libs/log";
|
import { logger } from "../libs/log";
|
||||||
import { sendBgMsg } from "../libs/msg";
|
import { sendBgMsg } from "../libs/msg";
|
||||||
|
import { isExt } from "../libs/client";
|
||||||
|
|
||||||
const SettingContext = createContext({
|
const SettingContext = createContext({
|
||||||
setting: DEFAULT_SETTING,
|
setting: DEFAULT_SETTING,
|
||||||
@@ -32,11 +33,22 @@ export function SettingProvider({ children }) {
|
|||||||
reload,
|
reload,
|
||||||
} = useStorage(STOKEY_SETTING, DEFAULT_SETTING, KV_SETTING_KEY);
|
} = useStorage(STOKEY_SETTING, DEFAULT_SETTING, KV_SETTING_KEY);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof setting?.darkMode === "boolean") {
|
||||||
|
update((currentSetting) => ({
|
||||||
|
...currentSetting,
|
||||||
|
darkMode: currentSetting.darkMode ? "dark" : "light",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [setting?.darkMode, update]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
logger.setLevel(setting?.logLevel);
|
logger.setLevel(setting?.logLevel);
|
||||||
await sendBgMsg(MSG_SET_LOGLEVEL, setting?.logLevel);
|
if (isExt) {
|
||||||
|
await sendBgMsg(MSG_SET_LOGLEVEL, setting?.logLevel);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to fetch log level, using default.", error);
|
logger.error("Failed to fetch log level, using default.", error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { ThemeProvider, createTheme } from "@mui/material/styles";
|
import { ThemeProvider, createTheme } from "@mui/material/styles";
|
||||||
import { CssBaseline, GlobalStyles } from "@mui/material";
|
import { CssBaseline, GlobalStyles } from "@mui/material";
|
||||||
import { useDarkMode } from "./ColorMode";
|
import { useDarkMode } from "./ColorMode";
|
||||||
@@ -11,6 +11,21 @@ import { THEME_DARK, THEME_LIGHT } from "../config";
|
|||||||
*/
|
*/
|
||||||
export default function Theme({ children, options, styles }) {
|
export default function Theme({ children, options, styles }) {
|
||||||
const { darkMode } = useDarkMode();
|
const { darkMode } = useDarkMode();
|
||||||
|
const [systemMode, setSystemMode] = useState(THEME_LIGHT);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window.matchMedia !== "function") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
const handleChange = () => {
|
||||||
|
setSystemMode(mediaQuery.matches ? THEME_DARK : THEME_LIGHT);
|
||||||
|
};
|
||||||
|
handleChange(); // Set initial value
|
||||||
|
mediaQuery.addEventListener("change", handleChange);
|
||||||
|
return () => mediaQuery.removeEventListener("change", handleChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const theme = useMemo(() => {
|
const theme = useMemo(() => {
|
||||||
let htmlFontSize = 16;
|
let htmlFontSize = 16;
|
||||||
try {
|
try {
|
||||||
@@ -23,16 +38,19 @@ export default function Theme({ children, options, styles }) {
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDarkMode =
|
||||||
|
darkMode === "dark" || (darkMode === "auto" && systemMode === THEME_DARK);
|
||||||
|
|
||||||
return createTheme({
|
return createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
mode: darkMode ? THEME_DARK : THEME_LIGHT,
|
mode: isDarkMode ? THEME_DARK : THEME_LIGHT,
|
||||||
},
|
},
|
||||||
typography: {
|
typography: {
|
||||||
htmlFontSize,
|
htmlFontSize,
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
}, [darkMode, options]);
|
}, [darkMode, options, systemMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import { limitNumber } from "../libs/utils";
|
import { limitNumber, limitFloat } from "../libs/utils";
|
||||||
|
|
||||||
function ValidationInput({ value, onChange, name, min, max, ...props }) {
|
function ValidationInput({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
name,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
isFloat = false,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
const [localValue, setLocalValue] = useState(value);
|
const [localValue, setLocalValue] = useState(value);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -21,7 +29,9 @@ function ValidationInput({ value, onChange, name, min, max, ...props }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatedValue = limitNumber(numValue, min, max);
|
const validatedValue = isFloat
|
||||||
|
? limitFloat(numValue, min, max)
|
||||||
|
: limitNumber(numValue, min, max);
|
||||||
|
|
||||||
if (validatedValue !== numValue) {
|
if (validatedValue !== numValue) {
|
||||||
setLocalValue(validatedValue);
|
setLocalValue(validatedValue);
|
||||||
|
|||||||
@@ -1,21 +1,3 @@
|
|||||||
import { MSG_XHR_DATA_YOUTUBE } from "./config";
|
import { XMLHttpRequestInjector } from "./subtitle/XMLHttpRequestInjector";
|
||||||
|
|
||||||
(function () {
|
XMLHttpRequestInjector();
|
||||||
const originalOpen = XMLHttpRequest.prototype.open;
|
|
||||||
XMLHttpRequest.prototype.open = function (...args) {
|
|
||||||
const url = args[1];
|
|
||||||
if (typeof url === "string" && url.includes("timedtext")) {
|
|
||||||
this.addEventListener("load", function () {
|
|
||||||
window.postMessage(
|
|
||||||
{
|
|
||||||
type: MSG_XHR_DATA_YOUTUBE,
|
|
||||||
url: this.responseURL,
|
|
||||||
response: this.responseText,
|
|
||||||
},
|
|
||||||
window.location.origin
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return originalOpen.apply(this, args);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
CACHE_NAME,
|
CACHE_NAME,
|
||||||
DEFAULT_CACHE_TIMEOUT,
|
DEFAULT_CACHE_TIMEOUT,
|
||||||
|
MSG_CLEAR_CACHES,
|
||||||
MSG_GET_HTTPCACHE,
|
MSG_GET_HTTPCACHE,
|
||||||
MSG_PUT_HTTPCACHE,
|
MSG_PUT_HTTPCACHE,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
@@ -15,7 +16,11 @@ import { blobToBase64 } from "./utils";
|
|||||||
*/
|
*/
|
||||||
export const tryClearCaches = async () => {
|
export const tryClearCaches = async () => {
|
||||||
try {
|
try {
|
||||||
caches.delete(CACHE_NAME);
|
if (isExt && !isBg) {
|
||||||
|
await sendBgMsg(MSG_CLEAR_CACHES);
|
||||||
|
} else {
|
||||||
|
await caches.delete(CACHE_NAME);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
kissLog("clean caches", err);
|
kissLog("clean caches", err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,29 @@
|
|||||||
// Function to inject inline JavaScript code
|
import { trustedTypesHelper } from "./trustedTypes";
|
||||||
export const injectInlineJs = (code) => {
|
|
||||||
const el = document.createElement("script");
|
|
||||||
el.setAttribute("data-source", "kiss-inject injectInlineJs");
|
|
||||||
el.setAttribute("type", "text/javascript");
|
|
||||||
el.textContent = code;
|
|
||||||
document.body?.appendChild(el);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to inject external JavaScript file
|
// Function to inject inline JavaScript code
|
||||||
export const injectExternalJs = (src, id = "kiss-translator-injector") => {
|
export const injectInlineJs = (code, id = "kiss-translator-inline-js") => {
|
||||||
if (document.getElementById(id)) {
|
if (document.getElementById(id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// const el = document.createElement("script");
|
const el = document.createElement("script");
|
||||||
// el.setAttribute("data-source", "kiss-inject injectExternalJs");
|
el.type = "text/javascript";
|
||||||
// el.setAttribute("type", "text/javascript");
|
el.id = id;
|
||||||
// el.setAttribute("src", src);
|
el.textContent = trustedTypesHelper.createScript(code);
|
||||||
// el.setAttribute("id", id);
|
(document.head || document.documentElement).appendChild(el);
|
||||||
// document.body?.appendChild(el);
|
};
|
||||||
const script = document.createElement("script");
|
|
||||||
script.id = id;
|
// Function to inject external JavaScript file
|
||||||
script.src = src;
|
export const injectExternalJs = (src, id = "kiss-translator-external-js") => {
|
||||||
(document.head || document.documentElement).appendChild(script);
|
if (document.getElementById(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const el = document.createElement("script");
|
||||||
|
el.type = "text/javascript";
|
||||||
|
el.id = id;
|
||||||
|
el.src = trustedTypesHelper.createScriptURL(src);
|
||||||
|
(document.head || document.documentElement).appendChild(el);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to inject internal CSS code
|
// Function to inject internal CSS code
|
||||||
|
|||||||
@@ -34,12 +34,18 @@ export const shortcutListener = (
|
|||||||
pressedKeys.delete(e.code);
|
pressedKeys.delete(e.code);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
pressedKeys.clear();
|
||||||
|
};
|
||||||
|
|
||||||
target.addEventListener("keydown", handleKeyDown);
|
target.addEventListener("keydown", handleKeyDown);
|
||||||
target.addEventListener("keyup", handleKeyUp);
|
target.addEventListener("keyup", handleKeyUp);
|
||||||
|
window.addEventListener("blur", handleBlur);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
target.removeEventListener("keydown", handleKeyDown);
|
target.removeEventListener("keydown", handleKeyDown);
|
||||||
target.removeEventListener("keyup", handleKeyUp);
|
target.removeEventListener("keyup", handleKeyUp);
|
||||||
|
window.removeEventListener("blur", handleBlur);
|
||||||
pressedKeys.clear();
|
pressedKeys.clear();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import { browser } from "./browser";
|
|||||||
import { isIframe, sendIframeMsg } from "./iframe";
|
import { isIframe, sendIframeMsg } from "./iframe";
|
||||||
import { TransboxManager } from "./tranbox";
|
import { TransboxManager } from "./tranbox";
|
||||||
import { InputTranslator } from "./inputTranslate";
|
import { InputTranslator } from "./inputTranslate";
|
||||||
|
import { trustedTypesHelper } from "./trustedTypes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Translator
|
* @class Translator
|
||||||
@@ -1021,10 +1022,19 @@ export class Translator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
inner.innerHTML = this.#restoreFromTranslation(
|
const htmlString = this.#restoreFromTranslation(
|
||||||
translatedText,
|
translatedText,
|
||||||
placeholderMap
|
placeholderMap
|
||||||
);
|
);
|
||||||
|
const trustedHTML = trustedTypesHelper.createHTML(htmlString);
|
||||||
|
|
||||||
|
// const parser = new DOMParser();
|
||||||
|
// const doc = parser.parseFromString(trustedHTML, "text/html");
|
||||||
|
// const innerElement = doc.body.firstChild;
|
||||||
|
// inner.replaceChildren(innerElement);
|
||||||
|
|
||||||
|
inner.innerHTML = trustedHTML;
|
||||||
|
|
||||||
this.#translationNodes.set(wrapper, {
|
this.#translationNodes.set(wrapper, {
|
||||||
nodes,
|
nodes,
|
||||||
isHide: hideOrigin,
|
isHide: hideOrigin,
|
||||||
@@ -1382,7 +1392,8 @@ export class Translator {
|
|||||||
injectJs && sendBgMsg(MSG_INJECT_JS, injectJs);
|
injectJs && sendBgMsg(MSG_INJECT_JS, injectJs);
|
||||||
injectCss && sendBgMsg(MSG_INJECT_CSS, injectCss);
|
injectCss && sendBgMsg(MSG_INJECT_CSS, injectCss);
|
||||||
} else {
|
} else {
|
||||||
injectJs && injectInlineJs(injectJs);
|
injectJs &&
|
||||||
|
injectInlineJs(injectJs, "kiss-translator-userinit-injector");
|
||||||
injectCss && injectInternalCss(injectCss);
|
injectCss && injectInternalCss(injectCss);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
33
src/libs/trustedTypes.js
Normal file
33
src/libs/trustedTypes.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
export const trustedTypesHelper = (() => {
|
||||||
|
const POLICY_NAME = "kiss-translator-policy";
|
||||||
|
let policy = null;
|
||||||
|
|
||||||
|
if (globalThis.trustedTypes && globalThis.trustedTypes.createPolicy) {
|
||||||
|
try {
|
||||||
|
policy = globalThis.trustedTypes.createPolicy(POLICY_NAME, {
|
||||||
|
createHTML: (string) => string,
|
||||||
|
createScript: (string) => string,
|
||||||
|
createScriptURL: (string) => string,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message.includes("already exists")) {
|
||||||
|
policy = globalThis.trustedTypes.policies.get(POLICY_NAME);
|
||||||
|
} else {
|
||||||
|
console.error("cont create Trusted Types", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createHTML: (htmlString) => {
|
||||||
|
return policy ? policy.createHTML(htmlString) : htmlString;
|
||||||
|
},
|
||||||
|
createScript: (scriptString) => {
|
||||||
|
return policy ? policy.createScript(scriptString) : scriptString;
|
||||||
|
},
|
||||||
|
createScriptURL: (urlString) => {
|
||||||
|
return policy ? policy.createScriptURL(urlString) : urlString;
|
||||||
|
},
|
||||||
|
isEnabled: () => policy !== null,
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -15,7 +15,7 @@ export const limitNumber = (num, min = 0, max = 100) => {
|
|||||||
return number;
|
return number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const limitFloat = (num, min = 0, max = 100) => {
|
export const limitFloat = (num, min = 0.0, max = 100.0) => {
|
||||||
const number = parseFloat(num);
|
const number = parseFloat(num);
|
||||||
if (Number.isNaN(number) || number < min) {
|
if (Number.isNaN(number) || number < min) {
|
||||||
return min;
|
return min;
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ export class BilingualSubtitleManager {
|
|||||||
p1.textContent = truncateWords(subtitle.text);
|
p1.textContent = truncateWords(subtitle.text);
|
||||||
|
|
||||||
const p2 = document.createElement("p");
|
const p2 = document.createElement("p");
|
||||||
p2.style.cssText = this.#setting.originStyle;
|
p2.style.cssText = this.#setting.translationStyle;
|
||||||
p2.textContent = truncateWords(subtitle.translation) || "...";
|
p2.textContent = truncateWords(subtitle.translation) || "...";
|
||||||
|
|
||||||
if (this.#setting.isBilingual) {
|
if (this.#setting.isBilingual) {
|
||||||
|
|||||||
19
src/subtitle/XMLHttpRequestInjector.js
Normal file
19
src/subtitle/XMLHttpRequestInjector.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export const XMLHttpRequestInjector = () => {
|
||||||
|
const originalOpen = XMLHttpRequest.prototype.open;
|
||||||
|
XMLHttpRequest.prototype.open = function (...args) {
|
||||||
|
const url = args[1];
|
||||||
|
if (typeof url === "string" && url.includes("timedtext")) {
|
||||||
|
this.addEventListener("load", function () {
|
||||||
|
window.postMessage(
|
||||||
|
{
|
||||||
|
type: "KISS_XHR_DATA_YOUTUBE",
|
||||||
|
url: this.responseURL,
|
||||||
|
response: this.responseText,
|
||||||
|
},
|
||||||
|
window.location.origin
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return originalOpen.apply(this, args);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -38,7 +38,6 @@ class YouTubeCaptionProvider {
|
|||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
window.addEventListener("message", (event) => {
|
window.addEventListener("message", (event) => {
|
||||||
if (event.source !== window) return;
|
|
||||||
if (event.data?.type === MSG_XHR_DATA_YOUTUBE) {
|
if (event.data?.type === MSG_XHR_DATA_YOUTUBE) {
|
||||||
const { url, response } = event.data;
|
const { url, response } = event.data;
|
||||||
if (url && response) {
|
if (url && response) {
|
||||||
@@ -66,23 +65,50 @@ class YouTubeCaptionProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get #videoEl() {
|
||||||
|
return document.querySelector(VIDEO_SELECT);
|
||||||
|
}
|
||||||
|
|
||||||
#moAds(adContainer) {
|
#moAds(adContainer) {
|
||||||
const adSlector = ".ytp-ad-player-overlay-layout";
|
const adLayoutSelector = ".ytp-ad-player-overlay-layout";
|
||||||
|
const skipBtnSelector =
|
||||||
|
".ytp-skip-ad-button, .ytp-ad-skip-button, .ytp-ad-skip-button-modern";
|
||||||
const observer = new MutationObserver((mutations) => {
|
const observer = new MutationObserver((mutations) => {
|
||||||
for (const mutation of mutations) {
|
for (const mutation of mutations) {
|
||||||
if (mutation.type === "childList") {
|
if (mutation.type === "childList") {
|
||||||
|
const videoEl = this.#videoEl;
|
||||||
mutation.addedNodes.forEach((node) => {
|
mutation.addedNodes.forEach((node) => {
|
||||||
if (node.nodeType === 1 && node.matches(adSlector)) {
|
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
||||||
|
|
||||||
|
if (node.matches(adLayoutSelector)) {
|
||||||
logger.debug("Youtube Provider: AD start playing!", node);
|
logger.debug("Youtube Provider: AD start playing!", node);
|
||||||
// todo: 顺带把广告快速跳过
|
// todo: 顺带把广告快速跳过
|
||||||
|
if (videoEl) {
|
||||||
|
videoEl.playbackRate = 16;
|
||||||
|
videoEl.currentTime = videoEl.duration;
|
||||||
|
}
|
||||||
if (this.#managerInstance) {
|
if (this.#managerInstance) {
|
||||||
this.#managerInstance.setIsAdPlaying(true);
|
this.#managerInstance.setIsAdPlaying(true);
|
||||||
}
|
}
|
||||||
|
} else if (node.matches(skipBtnSelector)) {
|
||||||
|
logger.debug("Youtube Provider: AD skip button!", node);
|
||||||
|
node.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
const skipBtn = node?.querySelector(skipBtnSelector);
|
||||||
|
if (skipBtn) {
|
||||||
|
logger.debug("Youtube Provider: AD skip button!!", skipBtn);
|
||||||
|
skipBtn.click();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mutation.removedNodes.forEach((node) => {
|
mutation.removedNodes.forEach((node) => {
|
||||||
if (node.nodeType === 1 && node.matches(adSlector)) {
|
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
||||||
|
|
||||||
|
if (node.matches(adLayoutSelector)) {
|
||||||
logger.debug("Youtube Provider: Ad ends!");
|
logger.debug("Youtube Provider: Ad ends!");
|
||||||
|
if (videoEl) {
|
||||||
|
videoEl.playbackRate = 1;
|
||||||
|
}
|
||||||
if (this.#managerInstance) {
|
if (this.#managerInstance) {
|
||||||
this.#managerInstance.setIsAdPlaying(false);
|
this.#managerInstance.setIsAdPlaying(false);
|
||||||
}
|
}
|
||||||
@@ -468,7 +494,7 @@ class YouTubeCaptionProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoEl = document.querySelector(VIDEO_SELECT);
|
const videoEl = this.#videoEl;
|
||||||
if (!videoEl) {
|
if (!videoEl) {
|
||||||
logger.warn("Youtube Provider: No video element found");
|
logger.warn("Youtube Provider: No video element found");
|
||||||
return;
|
return;
|
||||||
@@ -879,7 +905,7 @@ class YouTubeCaptionProvider {
|
|||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
});
|
});
|
||||||
|
|
||||||
const videoEl = document.querySelector(VIDEO_SELECT);
|
const videoEl = this.#videoEl;
|
||||||
const videoContainer = videoEl?.parentElement?.parentElement;
|
const videoContainer = videoEl?.parentElement?.parentElement;
|
||||||
if (videoContainer) {
|
if (videoContainer) {
|
||||||
videoContainer.appendChild(notificationEl);
|
videoContainer.appendChild(notificationEl);
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import { DEFAULT_API_SETTING } from "../config/api.js";
|
|||||||
import { DEFAULT_SUBTITLE_SETTING } from "../config/setting.js";
|
import { DEFAULT_SUBTITLE_SETTING } from "../config/setting.js";
|
||||||
import { injectExternalJs } from "../libs/injector.js";
|
import { injectExternalJs } from "../libs/injector.js";
|
||||||
import { logger } from "../libs/log.js";
|
import { logger } from "../libs/log.js";
|
||||||
|
import { XMLHttpRequestInjector } from "./XMLHttpRequestInjector.js";
|
||||||
|
import { injectInlineJs } from "../libs/injector.js";
|
||||||
|
|
||||||
const providers = [
|
const providers = [
|
||||||
{ pattern: "https://www.youtube.com", start: YouTubeInitializer },
|
{ pattern: "https://www.youtube.com", start: YouTubeInitializer },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function runSubtitle({ href, setting }) {
|
export function runSubtitle({ href, setting, isUserscript }) {
|
||||||
try {
|
try {
|
||||||
const subtitleSetting = setting.subtitleSetting || DEFAULT_SUBTITLE_SETTING;
|
const subtitleSetting = setting.subtitleSetting || DEFAULT_SUBTITLE_SETTING;
|
||||||
if (!subtitleSetting.enabled) {
|
if (!subtitleSetting.enabled) {
|
||||||
@@ -19,9 +21,13 @@ export function runSubtitle({ href, setting }) {
|
|||||||
|
|
||||||
const provider = providers.find((item) => isMatch(href, item.pattern));
|
const provider = providers.find((item) => isMatch(href, item.pattern));
|
||||||
if (provider) {
|
if (provider) {
|
||||||
const id = "kiss-translator-injector";
|
const id = "kiss-translator-xmlHttp-injector";
|
||||||
const src = browser.runtime.getURL("injector.js");
|
if (isUserscript) {
|
||||||
injectExternalJs(src, id);
|
injectInlineJs(`(${XMLHttpRequestInjector})()`, id);
|
||||||
|
} else {
|
||||||
|
const src = browser.runtime.getURL("injector.js");
|
||||||
|
injectExternalJs(src, id);
|
||||||
|
}
|
||||||
|
|
||||||
const apiSetting =
|
const apiSetting =
|
||||||
setting.transApis.find(
|
setting.transApis.find(
|
||||||
|
|||||||
@@ -299,8 +299,9 @@ function ApiFields({ apiSlug, isUserApi, deleteApi }) {
|
|||||||
name="temperature"
|
name="temperature"
|
||||||
value={temperature}
|
value={temperature}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
min={0}
|
min={0.0}
|
||||||
max={2}
|
max={2.0}
|
||||||
|
isFloat={true}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={12} md={6} lg={3}>
|
<Grid item xs={12} sm={12} md={6} lg={3}>
|
||||||
|
|||||||
@@ -2,12 +2,19 @@ import IconButton from "@mui/material/IconButton";
|
|||||||
import { useDarkMode } from "../../hooks/ColorMode";
|
import { useDarkMode } from "../../hooks/ColorMode";
|
||||||
import LightModeIcon from "@mui/icons-material/LightMode";
|
import LightModeIcon from "@mui/icons-material/LightMode";
|
||||||
import DarkModeIcon from "@mui/icons-material/DarkMode";
|
import DarkModeIcon from "@mui/icons-material/DarkMode";
|
||||||
|
import BrightnessAutoIcon from "@mui/icons-material/BrightnessAuto";
|
||||||
|
|
||||||
export default function DarkModeButton() {
|
export default function DarkModeButton() {
|
||||||
const { darkMode, toggleDarkMode } = useDarkMode();
|
const { darkMode, toggleDarkMode } = useDarkMode();
|
||||||
return (
|
return (
|
||||||
<IconButton onClick={toggleDarkMode} color="inherit">
|
<IconButton sx={{ ml: 1 }} onClick={toggleDarkMode} color="inherit">
|
||||||
{darkMode ? <LightModeIcon /> : <DarkModeIcon />}
|
{darkMode === "dark" ? (
|
||||||
|
<DarkModeIcon />
|
||||||
|
) : darkMode === "light" ? (
|
||||||
|
<LightModeIcon />
|
||||||
|
) : (
|
||||||
|
<BrightnessAutoIcon />
|
||||||
|
)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -471,11 +471,9 @@ export default function Popup({ setShowPopup, translator }) {
|
|||||||
<Button variant="text" onClick={handleSaveRule}>
|
<Button variant="text" onClick={handleSaveRule}>
|
||||||
{i18n("save_rule")}
|
{i18n("save_rule")}
|
||||||
</Button>
|
</Button>
|
||||||
{!isExt && (
|
<Button variant="text" onClick={handleClearCache}>
|
||||||
<Button variant="text" onClick={handleClearCache}>
|
{i18n("clear_cache")}
|
||||||
{i18n("clear_cache")}
|
</Button>
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button variant="text" onClick={handleOpenSetting}>
|
<Button variant="text" onClick={handleOpenSetting}>
|
||||||
{i18n("setting")}
|
{i18n("setting")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default function TranCont({
|
|||||||
<Box>
|
<Box>
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
label={`${i18n("translated_text")} - ${apiSetting.apiSlug}`}
|
label={`${i18n("translated_text")} - ${apiSetting.apiName}`}
|
||||||
// disabled
|
// disabled
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
|
|||||||
Reference in New Issue
Block a user