feat: inject user js/css

This commit is contained in:
Gabe Yuan
2024-03-14 18:06:28 +08:00
parent 9d4c596b4b
commit 2eabb7d5ac
10 changed files with 213 additions and 7 deletions

View File

@@ -44,7 +44,7 @@
"description": "__MSG_open_options__" "description": "__MSG_open_options__"
} }
}, },
"permissions": ["<all_urls>", "storage", "contextMenus"], "permissions": ["<all_urls>", "storage", "contextMenus", "scripting"],
"icons": { "icons": {
"16": "images/logo16.png", "16": "images/logo16.png",
"32": "images/logo32.png", "32": "images/logo32.png",

View File

@@ -45,7 +45,7 @@
"description": "__MSG_open_options__" "description": "__MSG_open_options__"
} }
}, },
"permissions": ["storage", "contextMenus"], "permissions": ["storage", "contextMenus", "scripting"],
"host_permissions": ["<all_urls>"], "host_permissions": ["<all_urls>"],
"icons": { "icons": {
"16": "images/logo16.png", "16": "images/logo16.png",

View File

@@ -10,6 +10,8 @@ import {
MSG_OPEN_TRANBOX, MSG_OPEN_TRANBOX,
MSG_CONTEXT_MENUS, MSG_CONTEXT_MENUS,
MSG_COMMAND_SHORTCUTS, MSG_COMMAND_SHORTCUTS,
MSG_INJECT_JS,
MSG_INJECT_CSS,
CMD_TOGGLE_TRANSLATE, CMD_TOGGLE_TRANSLATE,
CMD_TOGGLE_STYLE, CMD_TOGGLE_STYLE,
CMD_OPEN_OPTIONS, CMD_OPEN_OPTIONS,
@@ -22,6 +24,8 @@ import { sendTabMsg } from "./libs/msg";
import { trySyncAllSubRules } from "./libs/subRules"; import { trySyncAllSubRules } from "./libs/subRules";
import { tryClearCaches } from "./libs"; import { tryClearCaches } from "./libs";
import { saveRule } from "./libs/rules"; import { saveRule } from "./libs/rules";
import { getCurTabId } from "./libs/msg";
import { injectInlineJs, injectInternalCss } from "./libs/injector";
globalThis.ContextType = "BACKGROUND"; globalThis.ContextType = "BACKGROUND";
@@ -139,6 +143,40 @@ browser.runtime.onMessage.addListener(
case MSG_SAVE_RULE: case MSG_SAVE_RULE:
saveRule(args); saveRule(args);
break; 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: case MSG_CONTEXT_MENUS:
const { contextMenuType } = args; const { contextMenuType } = args;
addContextMenus(contextMenuType); addContextMenus(contextMenuType);

View File

@@ -407,6 +407,22 @@ export const I18N = {
zh: `0、支持正则表达式匹配。1、多条术语用换行或分号“;”隔开。2、术语和译文用英文逗号“,”隔开。3、没有译文视为不翻译术语。4、留空表示采用全局设置。`, 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.`, 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: { root_selector: {
zh: `根选择器`, zh: `根选择器`,
en: `Root Selector`, en: `Root Selector`,
@@ -759,4 +775,8 @@ export const I18N = {
zh: `是否同时翻译页面标题`, zh: `是否同时翻译页面标题`,
en: `Translate Page Title`, en: `Translate Page Title`,
}, },
more: {
zh: `更多`,
en: `More`,
},
}; };

View File

@@ -66,6 +66,8 @@ export const MSG_TRANS_PUTRULE = "trans_putrule";
export const MSG_TRANS_CURRULE = "trans_currule"; export const MSG_TRANS_CURRULE = "trans_currule";
export const MSG_CONTEXT_MENUS = "context_menus"; export const MSG_CONTEXT_MENUS = "context_menus";
export const MSG_COMMAND_SHORTCUTS = "command_shortcuts"; 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_LIGHT = "light";
export const THEME_DARK = "dark"; export const THEME_DARK = "dark";
@@ -335,6 +337,10 @@ export const GLOBLA_RULE = {
transOpen: "false", transOpen: "false",
bgColor: "", bgColor: "",
textDiyStyle: "", textDiyStyle: "",
selectStyle: "-webkit-line-clamp: unset; max-height: none; height: auto;",
parentStyle: "-webkit-line-clamp: unset; max-height: none; height: auto;",
injectJs: "",
injectCss: "",
}; };
// 输入框翻译 // 输入框翻译

View File

@@ -18,6 +18,10 @@ export const DEFAULT_RULE = {
transOpen: GLOBAL_KEY, transOpen: GLOBAL_KEY,
bgColor: "", bgColor: "",
textDiyStyle: "", textDiyStyle: "",
selectStyle: "",
parentStyle: "",
injectJs: "",
injectCss: "",
}; };
const DEFAULT_DIY_STYLE = `color: #666; const DEFAULT_DIY_STYLE = `color: #666;

35
src/libs/injector.js Normal file
View File

@@ -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);
};

View File

@@ -68,6 +68,10 @@ export const matchRule = async (
rule.selector = rule.selector?.trim() || globalRule.selector; rule.selector = rule.selector?.trim() || globalRule.selector;
rule.keepSelector = rule.keepSelector?.trim() || globalRule.keepSelector; rule.keepSelector = rule.keepSelector?.trim() || globalRule.keepSelector;
rule.terms = rule.terms?.trim() || globalRule.terms; 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) { if (rule.textStyle === GLOBAL_KEY) {
rule.textStyle = globalRule.textStyle; rule.textStyle = globalRule.textStyle;
rule.bgColor = globalRule.bgColor; rule.bgColor = globalRule.bgColor;
@@ -116,6 +120,10 @@ export const checkRules = (rules) => {
selector, selector,
keepSelector, keepSelector,
terms, terms,
selectStyle,
parentStyle,
injectJs,
injectCss,
translator, translator,
fromLang, fromLang,
toLang, toLang,
@@ -128,6 +136,10 @@ export const checkRules = (rules) => {
selector: type(selector) === "string" ? selector : "", selector: type(selector) === "string" ? selector : "",
keepSelector: type(keepSelector) === "string" ? keepSelector : "", keepSelector: type(keepSelector) === "string" ? keepSelector : "",
terms: type(terms) === "string" ? terms : "", 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 : "", bgColor: type(bgColor) === "string" ? bgColor : "",
textDiyStyle: type(textDiyStyle) === "string" ? textDiyStyle : "", textDiyStyle: type(textDiyStyle) === "string" ? textDiyStyle : "",
translator: matchValue([GLOBAL_KEY, ...OPT_TRANS_ALL], translator), translator: matchValue([GLOBAL_KEY, ...OPT_TRANS_ALL], translator),

View File

@@ -4,6 +4,8 @@ import {
TRANS_MIN_LENGTH, TRANS_MIN_LENGTH,
TRANS_MAX_LENGTH, TRANS_MAX_LENGTH,
MSG_TRANS_CURRULE, MSG_TRANS_CURRULE,
MSG_INJECT_JS,
MSG_INJECT_CSS,
OPT_STYLE_DASHLINE, OPT_STYLE_DASHLINE,
OPT_STYLE_FUZZY, OPT_STYLE_FUZZY,
SHADOW_KEY, SHADOW_KEY,
@@ -17,6 +19,7 @@ import { updateFetchPool, clearFetchPool } from "./fetch";
import { debounce, genEventName } from "./utils"; import { debounce, genEventName } from "./utils";
import { runFixer } from "./webfix"; import { runFixer } from "./webfix";
import { apiTranslate } from "../apis"; import { apiTranslate } from "../apis";
import { sendBgMsg } from "./msg";
/** /**
* 翻译类 * 翻译类
@@ -262,7 +265,8 @@ export class Translator {
}; };
_register = () => { _register = () => {
if (this._rule.fromLang === this._rule.toLang) { const { fromLang, toLang, injectJs, injectCss } = this._rule;
if (fromLang === toLang) {
return; return;
} }
@@ -271,6 +275,10 @@ export class Translator {
runFixer(this._fixerSetting); runFixer(this._fixerSetting);
} }
// 注入用户JS/CSS
injectJs && sendBgMsg(MSG_INJECT_JS, injectJs);
injectCss && sendBgMsg(MSG_INJECT_CSS, injectCss);
// 搜索节点 // 搜索节点
this._queryNodes(); 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._rootNodes.clear();
this._tranNodes.clear(); this._tranNodes.clear();
@@ -500,12 +513,11 @@ export class Translator {
// if (this._setting.transOnly) { // if (this._setting.transOnly) {
// el.innerHTML = ""; // el.innerHTML = "";
// } // }
const { selectStyle, parentStyle } = this._rule;
el.appendChild(traEl); el.appendChild(traEl);
el.style.cssText += el.style.cssText += selectStyle;
"-webkit-line-clamp: unset; max-height: none; height: auto;";
if (el.parentElement) { if (el.parentElement) {
el.parentElement.style.cssText += el.parentElement.style.cssText += parentStyle;
"-webkit-line-clamp: unset; max-height: none; height: auto;";
} }
// console.log({ q, keeps }); // console.log({ q, keeps });

View File

@@ -62,11 +62,16 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
const [disabled, setDisabled] = useState(editMode); const [disabled, setDisabled] = useState(editMode);
const [errors, setErrors] = useState({}); const [errors, setErrors] = useState({});
const [formValues, setFormValues] = useState(initFormValues); const [formValues, setFormValues] = useState(initFormValues);
const [showMore, setShowMore] = useState(!rules);
const { const {
pattern, pattern,
selector, selector,
keepSelector = "", keepSelector = "",
terms = "", terms = "",
selectStyle = "",
parentStyle = "",
injectJs = "",
injectCss = "",
translator, translator,
fromLang, fromLang,
toLang, toLang,
@@ -312,6 +317,47 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
</Grid> </Grid>
</Box> </Box>
{showMore && (
<>
<TextField
size="small"
label={i18n("selector_style")}
name="selectStyle"
value={selectStyle}
disabled={disabled}
onChange={handleChange}
multiline
/>
<TextField
size="small"
label={i18n("selector_parent_style")}
name="parentStyle"
value={parentStyle}
disabled={disabled}
onChange={handleChange}
multiline
/>
<TextField
size="small"
label={i18n("inject_js")}
name="injectJs"
value={injectJs}
disabled={disabled}
onChange={handleChange}
multiline
/>
<TextField
size="small"
label={i18n("inject_css")}
name="injectCss"
value={injectCss}
disabled={disabled}
onChange={handleChange}
multiline
/>
</>
)}
{textStyle === OPT_STYLE_DIY && ( {textStyle === OPT_STYLE_DIY && (
<TextField <TextField
size="small" size="small"
@@ -353,6 +399,17 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
{i18n("delete")} {i18n("delete")}
</Button> </Button>
)} )}
{!showMore && (
<Button
size="small"
variant="text"
onClick={() => {
setShowMore(true);
}}
>
{i18n("more")}
</Button>
)}
</> </>
) : ( ) : (
<> <>
@@ -366,6 +423,17 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
> >
{i18n("cancel")} {i18n("cancel")}
</Button> </Button>
{!showMore && (
<Button
size="small"
variant="text"
onClick={() => {
setShowMore(true);
}}
>
{i18n("more")}
</Button>
)}
</> </>
)} )}
</Stack> </Stack>
@@ -378,6 +446,17 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
<Button size="small" variant="outlined" onClick={handleCancel}> <Button size="small" variant="outlined" onClick={handleCancel}>
{i18n("cancel")} {i18n("cancel")}
</Button> </Button>
{!showMore && (
<Button
size="small"
variant="text"
onClick={() => {
setShowMore(true);
}}
>
{i18n("more")}
</Button>
)}
</Stack> </Stack>
))} ))}
</Stack> </Stack>