From 2eabb7d5ac91b3e3a248815aeb3c0c86729137d7 Mon Sep 17 00:00:00 2001 From: Gabe Yuan Date: Thu, 14 Mar 2024 18:06:28 +0800 Subject: [PATCH] feat: inject user js/css --- public/manifest.firefox.json | 2 +- public/manifest.json | 2 +- src/background.js | 38 +++++++++++++++++ src/config/i18n.js | 20 +++++++++ src/config/index.js | 6 +++ src/config/rules.js | 4 ++ src/libs/injector.js | 35 ++++++++++++++++ src/libs/rules.js | 12 ++++++ src/libs/translator.js | 22 +++++++--- src/views/Options/Rules.js | 79 ++++++++++++++++++++++++++++++++++++ 10 files changed, 213 insertions(+), 7 deletions(-) create mode 100644 src/libs/injector.js diff --git a/public/manifest.firefox.json b/public/manifest.firefox.json index 3e5f4bd..e291d92 100644 --- a/public/manifest.firefox.json +++ b/public/manifest.firefox.json @@ -44,7 +44,7 @@ "description": "__MSG_open_options__" } }, - "permissions": ["", "storage", "contextMenus"], + "permissions": ["", "storage", "contextMenus", "scripting"], "icons": { "16": "images/logo16.png", "32": "images/logo32.png", diff --git a/public/manifest.json b/public/manifest.json index 1d0787e..1fba22e 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -45,7 +45,7 @@ "description": "__MSG_open_options__" } }, - "permissions": ["storage", "contextMenus"], + "permissions": ["storage", "contextMenus", "scripting"], "host_permissions": [""], "icons": { "16": "images/logo16.png", diff --git a/src/background.js b/src/background.js index 5c17ac5..5dc14e5 100644 --- a/src/background.js +++ b/src/background.js @@ -10,6 +10,8 @@ import { MSG_OPEN_TRANBOX, MSG_CONTEXT_MENUS, MSG_COMMAND_SHORTCUTS, + MSG_INJECT_JS, + MSG_INJECT_CSS, CMD_TOGGLE_TRANSLATE, CMD_TOGGLE_STYLE, CMD_OPEN_OPTIONS, @@ -22,6 +24,8 @@ import { sendTabMsg } from "./libs/msg"; import { trySyncAllSubRules } from "./libs/subRules"; import { tryClearCaches } from "./libs"; import { saveRule } from "./libs/rules"; +import { getCurTabId } from "./libs/msg"; +import { injectInlineJs, injectInternalCss } from "./libs/injector"; globalThis.ContextType = "BACKGROUND"; @@ -139,6 +143,40 @@ browser.runtime.onMessage.addListener( case MSG_SAVE_RULE: saveRule(args); break; + case MSG_INJECT_JS: + getCurTabId() + .then((tabId) => + browser.scripting.executeScript({ + target: { tabId: tabId, allFrames: true }, + func: injectInlineJs, + args: [args], + world: "MAIN", + }) + ) + .then(() => { + // skip + }) + .catch((error) => { + sendResponse({ error: error.message }); + }); + break; + case MSG_INJECT_CSS: + getCurTabId() + .then((tabId) => + browser.scripting.executeScript({ + target: { tabId: tabId, allFrames: true }, + func: injectInternalCss, + args: [args], + world: "MAIN", + }) + ) + .then(() => { + // skip + }) + .catch((error) => { + sendResponse({ error: error.message }); + }); + break; case MSG_CONTEXT_MENUS: const { contextMenuType } = args; addContextMenus(contextMenuType); diff --git a/src/config/i18n.js b/src/config/i18n.js index 35f6489..e581f7b 100644 --- a/src/config/i18n.js +++ b/src/config/i18n.js @@ -407,6 +407,22 @@ export const I18N = { zh: `0、支持正则表达式匹配。1、多条术语用换行或分号“;”隔开。2、术语和译文用英文逗号“,”隔开。3、没有译文视为不翻译术语。4、留空表示采用全局设置。`, en: `0. Supports regular expression matching. 1. Separate multiple terms with newlines or semicolons ";". 2. Terms and translations are separated by English commas ",". 3. If there is no translation, the term will be deemed not to be translated. 4. Leave blank to adopt the global setting.`, }, + selector_style: { + zh: `选择器样式`, + en: `Selector Style`, + }, + selector_parent_style: { + zh: `选择器父样式`, + en: `Selector Parent Style`, + }, + inject_js: { + zh: `注入JS`, + en: `Inject JS`, + }, + inject_css: { + zh: `注入CSS`, + en: `Inject CSS`, + }, root_selector: { zh: `根选择器`, en: `Root Selector`, @@ -759,4 +775,8 @@ export const I18N = { zh: `是否同时翻译页面标题`, en: `Translate Page Title`, }, + more: { + zh: `更多`, + en: `More`, + }, }; diff --git a/src/config/index.js b/src/config/index.js index d254c8d..35a94a4 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -66,6 +66,8 @@ export const MSG_TRANS_PUTRULE = "trans_putrule"; export const MSG_TRANS_CURRULE = "trans_currule"; export const MSG_CONTEXT_MENUS = "context_menus"; export const MSG_COMMAND_SHORTCUTS = "command_shortcuts"; +export const MSG_INJECT_JS = "inject_js"; +export const MSG_INJECT_CSS = "inject_css"; export const THEME_LIGHT = "light"; export const THEME_DARK = "dark"; @@ -335,6 +337,10 @@ export const GLOBLA_RULE = { transOpen: "false", bgColor: "", textDiyStyle: "", + selectStyle: "-webkit-line-clamp: unset; max-height: none; height: auto;", + parentStyle: "-webkit-line-clamp: unset; max-height: none; height: auto;", + injectJs: "", + injectCss: "", }; // 输入框翻译 diff --git a/src/config/rules.js b/src/config/rules.js index 31c1ba3..b4fcc8e 100644 --- a/src/config/rules.js +++ b/src/config/rules.js @@ -18,6 +18,10 @@ export const DEFAULT_RULE = { transOpen: GLOBAL_KEY, bgColor: "", textDiyStyle: "", + selectStyle: "", + parentStyle: "", + injectJs: "", + injectCss: "", }; const DEFAULT_DIY_STYLE = `color: #666; diff --git a/src/libs/injector.js b/src/libs/injector.js new file mode 100644 index 0000000..d613199 --- /dev/null +++ b/src/libs/injector.js @@ -0,0 +1,35 @@ +// Function to inject inline JavaScript code +export const injectInlineJs = (code) => { + const el = document.createElement("script"); + el.setAttribute("data-source", "KISS-Calendar injectInlineJs"); + el.setAttribute("type", "text/javascript"); + el.textContent = code; + document.body.appendChild(el); +}; + +// Function to inject external JavaScript file +export const injectExternalJs = (src) => { + const el = document.createElement("script"); + el.setAttribute("data-source", "KISS-Calendar injectExternalJs"); + el.setAttribute("type", "text/javascript"); + el.setAttribute("src", src); + document.body.appendChild(el); +}; + +// Function to inject internal CSS code +export const injectInternalCss = (styles) => { + const el = document.createElement("style"); + el.setAttribute("data-source", "KISS-Calendar injectInternalCss"); + el.textContent = styles; + document.head.appendChild(el); +}; + +// Function to inject external CSS file +export const injectExternalCss = (href) => { + const el = document.createElement("link"); + el.setAttribute("data-source", "KISS-Calendar injectExternalCss"); + el.setAttribute("rel", "stylesheet"); + el.setAttribute("type", "text/css"); + el.setAttribute("href", href); + document.head.appendChild(el); +}; diff --git a/src/libs/rules.js b/src/libs/rules.js index 7fd82e8..ffaed13 100644 --- a/src/libs/rules.js +++ b/src/libs/rules.js @@ -68,6 +68,10 @@ export const matchRule = async ( rule.selector = rule.selector?.trim() || globalRule.selector; rule.keepSelector = rule.keepSelector?.trim() || globalRule.keepSelector; rule.terms = rule.terms?.trim() || globalRule.terms; + rule.selectStyle = rule.selectStyle?.trim() || globalRule.selectStyle; + rule.parentStyle = rule.parentStyle?.trim() || globalRule.parentStyle; + rule.injectJs = rule.injectJs?.trim() || globalRule.injectJs; + rule.injectCss = rule.injectCss?.trim() || globalRule.injectCss; if (rule.textStyle === GLOBAL_KEY) { rule.textStyle = globalRule.textStyle; rule.bgColor = globalRule.bgColor; @@ -116,6 +120,10 @@ export const checkRules = (rules) => { selector, keepSelector, terms, + selectStyle, + parentStyle, + injectJs, + injectCss, translator, fromLang, toLang, @@ -128,6 +136,10 @@ export const checkRules = (rules) => { selector: type(selector) === "string" ? selector : "", keepSelector: type(keepSelector) === "string" ? keepSelector : "", terms: type(terms) === "string" ? terms : "", + selectStyle: type(selectStyle) === "string" ? selectStyle : "", + parentStyle: type(parentStyle) === "string" ? parentStyle : "", + injectJs: type(injectJs) === "string" ? injectJs : "", + injectCss: type(injectCss) === "string" ? injectCss : "", bgColor: type(bgColor) === "string" ? bgColor : "", textDiyStyle: type(textDiyStyle) === "string" ? textDiyStyle : "", translator: matchValue([GLOBAL_KEY, ...OPT_TRANS_ALL], translator), diff --git a/src/libs/translator.js b/src/libs/translator.js index 3fe1567..9b5c581 100644 --- a/src/libs/translator.js +++ b/src/libs/translator.js @@ -4,6 +4,8 @@ import { TRANS_MIN_LENGTH, TRANS_MAX_LENGTH, MSG_TRANS_CURRULE, + MSG_INJECT_JS, + MSG_INJECT_CSS, OPT_STYLE_DASHLINE, OPT_STYLE_FUZZY, SHADOW_KEY, @@ -17,6 +19,7 @@ import { updateFetchPool, clearFetchPool } from "./fetch"; import { debounce, genEventName } from "./utils"; import { runFixer } from "./webfix"; import { apiTranslate } from "../apis"; +import { sendBgMsg } from "./msg"; /** * 翻译类 @@ -262,7 +265,8 @@ export class Translator { }; _register = () => { - if (this._rule.fromLang === this._rule.toLang) { + const { fromLang, toLang, injectJs, injectCss } = this._rule; + if (fromLang === toLang) { return; } @@ -271,6 +275,10 @@ export class Translator { runFixer(this._fixerSetting); } + // 注入用户JS/CSS + injectJs && sendBgMsg(MSG_INJECT_JS, injectJs); + injectCss && sendBgMsg(MSG_INJECT_CSS, injectCss); + // 搜索节点 this._queryNodes(); @@ -397,6 +405,11 @@ export class Translator { } }); + // 移除用户JS/CSS + document + .querySelectorAll(`[data-source^="KISS-Calendar"]`) + ?.forEach((el) => el.remove()); + // 清空节点集合 this._rootNodes.clear(); this._tranNodes.clear(); @@ -500,12 +513,11 @@ export class Translator { // if (this._setting.transOnly) { // el.innerHTML = ""; // } + const { selectStyle, parentStyle } = this._rule; el.appendChild(traEl); - el.style.cssText += - "-webkit-line-clamp: unset; max-height: none; height: auto;"; + el.style.cssText += selectStyle; if (el.parentElement) { - el.parentElement.style.cssText += - "-webkit-line-clamp: unset; max-height: none; height: auto;"; + el.parentElement.style.cssText += parentStyle; } // console.log({ q, keeps }); diff --git a/src/views/Options/Rules.js b/src/views/Options/Rules.js index 4354e24..06d9287 100644 --- a/src/views/Options/Rules.js +++ b/src/views/Options/Rules.js @@ -62,11 +62,16 @@ function RuleFields({ rule, rules, setShow, setKeyword }) { const [disabled, setDisabled] = useState(editMode); const [errors, setErrors] = useState({}); const [formValues, setFormValues] = useState(initFormValues); + const [showMore, setShowMore] = useState(!rules); const { pattern, selector, keepSelector = "", terms = "", + selectStyle = "", + parentStyle = "", + injectJs = "", + injectCss = "", translator, fromLang, toLang, @@ -312,6 +317,47 @@ function RuleFields({ rule, rules, setShow, setKeyword }) { + {showMore && ( + <> + + + + + + )} + {textStyle === OPT_STYLE_DIY && ( )} + {!showMore && ( + + )} ) : ( <> @@ -366,6 +423,17 @@ function RuleFields({ rule, rules, setShow, setKeyword }) { > {i18n("cancel")} + {!showMore && ( + + )} )} @@ -378,6 +446,17 @@ function RuleFields({ rule, rules, setShow, setKeyword }) { + {!showMore && ( + + )} ))}