diff --git a/.env b/.env index 279d991..6cd235e 100644 --- a/.env +++ b/.env @@ -17,6 +17,8 @@ REACT_APP_RULESURL=https://fishjar.github.io/kiss-rules/kiss-rules.json REACT_APP_RULESURL_ON=https://fishjar.github.io/kiss-rules/kiss-rules-on.json REACT_APP_RULESURL_OFF=https://fishjar.github.io/kiss-rules/kiss-rules-off.json +REACT_APP_WEBFIXURL=https://fishjar.github.io/kiss-rules/kiss-webfix.json + REACT_APP_VERSIONFILE=https://fishjar.github.io/kiss-translator/version.txt REACT_APP_VERSIONFILE2=https://kiss-translator.rayjar.com/version.txt diff --git a/src/config/i18n.js b/src/config/i18n.js index 5b67700..802634d 100644 --- a/src/config/i18n.js +++ b/src/config/i18n.js @@ -129,6 +129,18 @@ export const I18N = { zh: `同步设置`, en: `Sync Setting`, }, + patch_setting: { + zh: `补丁设置`, + en: `Patch Setting`, + }, + patch_setting_help: { + zh: `针对一些特殊网站的修正脚本,以便翻译软件得到更好的展示效果。`, + en: `Corrected scripts for some special websites so that the translation software can get better display results.`, + }, + inject_webfix: { + zh: `注入修复补丁`, + en: `Inject Webfix`, + }, about: { zh: `关于`, en: `About`, diff --git a/src/config/index.js b/src/config/index.js index c00fcc8..b1857f3 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -25,6 +25,7 @@ export const STOKEY_RULES = `${APP_NAME}_rules`; export const STOKEY_SYNC = `${APP_NAME}_sync`; export const STOKEY_FAB = `${APP_NAME}_fab`; export const STOKEY_RULESCACHE_PREFIX = `${APP_NAME}_rulescache_`; +export const STOKEY_WEBFIXCACHE_PREFIX = `${APP_NAME}_webfixcache_`; export const CMD_TOGGLE_TRANSLATE = "toggleTranslate"; export const CMD_TOGGLE_STYLE = "toggleStyle"; @@ -262,6 +263,7 @@ export const DEFAULT_SETTING = { newlineLength: TRANS_NEWLINE_LENGTH, clearCache: false, // 是否在浏览器下次启动时清除缓存 injectRules: true, // 是否注入订阅规则 + injectWebfix: true, // 是否注入修复补丁 subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表 owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则 transApis: DEFAULT_TRANS_APIS, // 翻译接口 diff --git a/src/content.js b/src/content.js index aca2d54..4fedee9 100644 --- a/src/content.js +++ b/src/content.js @@ -9,6 +9,7 @@ import { getSettingWithDefault, getRulesWithDefault } from "./libs/storage"; import { Translator } from "./libs/translator"; import { isIframe } from "./libs/iframe"; import { matchRule } from "./libs/rules"; +import { webfix } from "./libs/webfix"; /** * 入口函数 @@ -19,6 +20,7 @@ const init = async () => { const rules = await getRulesWithDefault(); const rule = await matchRule(rules, href, setting); const translator = new Translator(rule, setting); + webfix(href, setting); // 监听消息 browser?.runtime.onMessage.addListener(async ({ action, args }) => { diff --git a/src/libs/webfix.js b/src/libs/webfix.js new file mode 100644 index 0000000..d629615 --- /dev/null +++ b/src/libs/webfix.js @@ -0,0 +1,130 @@ +import { isMatch } from "./utils"; + +/** + * 需要修复的站点列表 + * - pattern 匹配网址 + * - selector 需要修复的选择器 + * - rootSlector 需要监听的选择器,可留空 + * - fixer 修复函数,可针对不同网址,选用不同修复函数 + */ +export const sites = [ + { + pattern: "www.phoronix.com", + selector: ".content", + rootSlector: "", + fixer: brFixer, + }, + { + pattern: "t.me/s/*", + selector: ".tgme_widget_message_text", + rootSlector: ".tgme_channel_history", + fixer: brFixer, + }, +]; + +/** + * 修复过的标记 + */ +const fixedSign = "kissfixed"; + +/** + * 采用 `br` 换行网站的修复函数 + * 目标是将 `br` 替换成 `p` + * @param {*} node + * @returns + */ +function brFixer(node) { + if (node.hasAttribute(fixedSign)) { + return; + } + node.setAttribute(fixedSign, "true"); + + var gapTags = ["BR", "WBR"]; + var newlineTags = [ + "DIV", + "UL", + "OL", + "LI", + "H1", + "H2", + "H3", + "H4", + "H5", + "H6", + "P", + "HR", + "PRE", + "TABLE", + ]; + + var html = ""; + node.childNodes.forEach(function (child, index) { + if (index === 0) { + html += "

"; + } + + if (gapTags.indexOf(child.nodeName) !== -1) { + html += "

"; + } else if (newlineTags.indexOf(child.nodeName) !== -1) { + html += "

" + child.outerHTML + "

"; + } else if (child.outerHTML) { + html += child.outerHTML; + } else if (child.nodeValue) { + html += child.nodeValue; + } + + if (index === node.childNodes.length - 1) { + html += "

"; + } + }); + node.innerHTML = html; +} + +/** + * 查找、监听节点,并执行修复函数 + * @param {*} selector + * @param {*} fixer + * @param {*} rootSlector + */ +function run(selector, fixer, rootSlector) { + var mutaObserver = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + mutation.addedNodes.forEach(function (addNode) { + addNode.querySelectorAll(selector).forEach(fixer); + }); + }); + }); + + var rootNodes = [document]; + if (rootSlector) { + rootNodes = document.querySelectorAll(rootSlector); + } + + rootNodes.forEach(function (rootNode) { + rootNode.querySelectorAll(selector).forEach(fixer); + mutaObserver.observe(rootNode, { + childList: true, + }); + }); +} + +/** + * 匹配站点 + */ +export function webfix(href, { injectWebfix }) { + try { + if (!injectWebfix) { + return; + } + + for (var i = 0; i < sites.length; i++) { + var site = sites[i]; + if (isMatch(href, site.pattern)) { + run(site.selector, site.fixer, site.rootSlector); + break; + } + } + } catch (err) { + console.error(`[kiss-webfix]: ${err.message}`); + } +} diff --git a/src/userscript.js b/src/userscript.js index f212c50..cf7991e 100644 --- a/src/userscript.js +++ b/src/userscript.js @@ -15,6 +15,7 @@ import { isIframe } from "./libs/iframe"; import { handlePing, injectScript } from "./libs/gm"; import { matchRule } from "./libs/rules"; import { genEventName } from "./libs/utils"; +import { webfix } from "./libs/webfix"; /** * 入口函数 @@ -50,6 +51,7 @@ const init = async () => { const rules = await getRulesWithDefault(); const rule = await matchRule(rules, href, setting); const translator = new Translator(rule, setting); + webfix(href, setting); if (isIframe) { // iframe diff --git a/src/views/Options/Navigator.js b/src/views/Options/Navigator.js index 7bf08d8..133b533 100644 --- a/src/views/Options/Navigator.js +++ b/src/views/Options/Navigator.js @@ -11,6 +11,7 @@ import DesignServicesIcon from "@mui/icons-material/DesignServices"; import { useI18n } from "../../hooks/I18n"; import SyncIcon from "@mui/icons-material/Sync"; import ApiIcon from "@mui/icons-material/Api"; +import SendTimeExtensionIcon from "@mui/icons-material/SendTimeExtension"; function LinkItem({ label, url, icon }) { const match = useMatch(url); @@ -49,6 +50,12 @@ export default function Navigator(props) { url: "/sync", icon: , }, + { + id: "webfix", + label: i18n("patch_setting"), + url: "/webfix", + icon: , + }, { id: "about", label: i18n("about"), url: "/about", icon: }, ]; return ( diff --git a/src/views/Options/Webfix.js b/src/views/Options/Webfix.js new file mode 100644 index 0000000..6f2ce3d --- /dev/null +++ b/src/views/Options/Webfix.js @@ -0,0 +1,89 @@ +import Stack from "@mui/material/Stack"; +import TextField from "@mui/material/TextField"; +import { useState } from "react"; +import { useI18n } from "../../hooks/I18n"; +import Typography from "@mui/material/Typography"; +import Accordion from "@mui/material/Accordion"; +import AccordionSummary from "@mui/material/AccordionSummary"; +import AccordionDetails from "@mui/material/AccordionDetails"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import Alert from "@mui/material/Alert"; +import Box from "@mui/material/Box"; +import { sites as webfixSites } from "../../libs/webfix"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Switch from "@mui/material/Switch"; +import { useSetting } from "../../hooks/Setting"; + +function ApiFields({ site }) { + const { selector, rootSlector } = site; + return ( + + + + + ); +} + +function ApiAccordion({ site }) { + const [expanded, setExpanded] = useState(false); + + const handleChange = (e) => { + setExpanded((pre) => !pre); + }; + + return ( + + }> + {site.pattern} + + + {expanded && } + + + ); +} + +export default function Webfix() { + const i18n = useI18n(); + const { setting, updateSetting } = useSetting(); + return ( + + + {i18n("patch_setting_help")} + + { + updateSetting({ + injectWebfix: !setting.injectWebfix, + }); + }} + /> + } + label={i18n("inject_webfix")} + /> + + + {webfixSites.map((site) => ( + + ))} + + + + ); +} diff --git a/src/views/Options/index.js b/src/views/Options/index.js index 97f693b..af02405 100644 --- a/src/views/Options/index.js +++ b/src/views/Options/index.js @@ -18,6 +18,7 @@ import Stack from "@mui/material/Stack"; import { adaptScript } from "../../libs/gm"; import Alert from "@mui/material/Alert"; import Apis from "./Apis"; +import Webfix from "./Webfix"; export default function Options() { const [error, setError] = useState(""); @@ -127,6 +128,7 @@ export default function Options() { } /> } /> } /> + } /> } />