FirefoxCustomize/StatusBar.uc.js at master · benzBrake/FirefoxCustomize
Ryan 收集的 Firefox 个性化相关资源. Contribute to benzBrake/FirefoxCustomize development by creating an account on GitHub.
github.com
那是启动时,第一次点击:
JavaScriptAlles anzeigen(function () { if (location.href !== 'chrome://browser/content/browser.xhtml') return; setTimeout(() => { const orig = document.getElementById('saveforreadlater_gmail_com-menuitem-_saveIt'); const ref = document.getElementById('context-take-screenshot'); contentAreaContextMenu.insertBefore(orig, ref.nextSibling); }, 20000); } ) ();
其子菜单中-Original后两个Element个的或要求的功能的Dropdowns的项目出现了位移。
那是基本菜单的因素是延期还是存在,但是更多。
因此,我的简单的因素就移。这个还不够。"难道(还有的部件clonen,它的功能形象,没有不和谐)(那是极度长的时间,然后超过)
因此我首先感谢就在帮忙。
(function () {
if (location.href !== 'chrome://browser/content/browser.xhtml')
return;
const ref = document.getElementById('context-take-screenshot');
if (ref) {
ref.after($C('menuitem', {
label: "Save all pages for read them later",
oncommand: 'event.target.parentNode.querySelector("#saveforreadlater_gmail_com-menuitem - _saveItAll").click(event);'
}));
ref.after($C('menuitem', {
label: "Save this page for Read Later",
oncommand: 'event.target.parentNode.querySelector("#saveforreadlater_gmail_com-menuitem-_saveIt").click(event);'
}));
}
var style = "data:text/css;charset=utf-8," + encodeURIComponent(`
#contentAreaContextMenu [id^="saveforreadlater_gmail_com-menuitem"] {
display: none;
}
`);
windowUtils.loadSheetUsingURIString(style, windowUtils.AUTHOR_SHEET);
function $C(tag, attrs) {
var el;
if (!tag) return el;
attrs = attrs || {};
el = document.createXULElement(tag);
if (attrs) Object.keys(attrs).forEach(function (key) {
el.setAttribute(key, attrs[key]);
});
return el;
}
})()
Alles anzeigen
Alles anzeigenDieses JavaScript ist ja sehr nett, aber um eine kostenlosen "API key" zu bekommen
müssen die Bankdaten angegeben werden!
Das ist nicht wirklich prickelnd und ich bekomme da "Bauchweh"!Geht es auch irgendwie ohne?
Und wer diese Japanische Schriftzeichen ändern möchte,
Zeile 83
menuItem.label = "Auswahl mit DeepL übersetzen";
und
Zeile 192
logo.textContent = "DeepL Übersetzer";
Wie ein Icon vor den Text des Menüs "gezaubert" wird, muss ich erst noch testen.
Macht nur im Moment keinen Sinn, da ohne API-Key das Script nicht funktioniert.
Und hier noch ein Mal der Code des JavaScripts im Original,
da bei mir die Seite schon einige Male nicht geladen wurde
JavaScriptAlles anzeigen// ==UserScript== // @name DeepL Translator // @include main // @compatibility Firefox 78+ // @description コンテクストメニューに選択テキストをDeepLで翻訳する機能を追加する // @note DeepLのAPIキー (無料版で可) が必要 // @charset UTF-8 // ==/UserScript== "use strict"; if (typeof window === "undefined" || globalThis !== window) { /* --- 設定ここから --- */ const apiKey = "Enter your API key here!"; const apiEndpoint = "https://api-free.deepl.com/v2"; const hotkey = { enabled: false, code: "ControlLeft", repeat: 2, timeout: 500, }; const defaultLang = "JA"; const supportedLangs = { //"BG": "Bulgarian", //"CS": "Czech", //"DA": "Danish", "DE": "German", //"EL": "Greek", //"EN-GB": "English (British)", "EN-US": "English (American)", "ES": "Spanish", //"ET": "Estonian", //"FI": "Finnish", "FR": "French", //"HU": "Hungarian", "IT": "Italian", "JA": "Japanese", //"LT": "Lithuanian", //"LV": "Latvian", //"NL": "Dutch", //"PL": "Polish", //"PT-PT": "Portuguese", //"PT-BR": "Portuguese (Brazilian)", //"RO": "Romanian", //"RU": "Russian", //"SK": "Slovak", //"SL": "Slovenian", //"SV": "Swedish", //"ZH": "Chinese", }; /* --- 設定ここまで --- */ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); ChromeUtils.defineModuleGetter(this, "ContentDOMReference", "resource://gre/modules/ContentDOMReference.jsm"); ChromeUtils.defineModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); if (!Services.appinfo.remoteType) { this.EXPORTED_SYMBOLS = ["DLTranslator", "DLTranslatorParent"]; try { const actorParams = { parent: { moduleURI: __URI__, }, child: { moduleURI: __URI__, events: {}, }, allFrames: true, messageManagerGroups: ["browsers"], matches: [`*://*/*`], }; if (hotkey.enabled) { if (hotkey.repeat <= 0) hotkey.repeat = 2; if (hotkey.timeout <= 0) hotkey.timeout = 500; actorParams.child.events.keyup = {}; } ChromeUtils.registerWindowActor("DLTranslator", actorParams); } catch(e) {Cu.reportError(e);} this.DLTranslator = new class { attachToWindow(win) { let menuPopup = win.document.getElementById("contentAreaContextMenu"); let menuItem = win.document.createXULElement("menuitem"); menuItem.label = "選択テキストを DeepL で翻訳"; menuItem.id = "menu-deepl-translate"; menuItem.addEventListener("command", this); menuPopup.appendChild(menuItem); menuPopup.addEventListener("popupshowing", this); win.addEventListener("unload", this, {once:true}); } detachFromWindow(win) { win.removeEventListener("unload", this, {once:true}); // this might be unnecessary, but do anyway const menu = win.document.getElementById("menu-deepl-translate"); menu.parentNode.removeEventListener("popupshowing", this); menu.parentNode.removeChild(menu); } handleEvent({type, target}) { switch(type) { case "popupshowing": this.handlePopup(target.ownerGlobal); break; case "command": this.beginTranslate(target.ownerGlobal.gContextMenu?.contentData); break; case "unload": this.detachFromWindow(target.ownerGlobal); break; } } handlePopup(win) { let selectionText = win.gContextMenu?.contentData?.selectionInfo?.text; win.document.getElementById("menu-deepl-translate").hidden = !selectionText; } beginTranslate(contextMenuContentData) { if (!contextMenuContentData) return; const win = contextMenuContentData.browser.ownerGlobal; const selectionText = contextMenuContentData.selectionInfo?.fullText; const targetIdentifier = contextMenuContentData.context?.targetIdentifier; const screenX = contextMenuContentData.context?.screenX ?? contextMenuContentData.context?.screenXDevPx / win.devicePixelRatio; const screenY = contextMenuContentData.context?.screenY ?? contextMenuContentData.context?.screenYDevPx / win.devicePixelRatio; const browser = contextMenuContentData.browser; const browserBoundingRect = browser.getBoundingClientRect(); const fixupX = browser.ownerGlobal.outerWidth - browserBoundingRect.left - browserBoundingRect.width; const fixupY = 20 + browser.ownerGlobal.outerHeight - browserBoundingRect.top - browserBoundingRect.height; const actor = contextMenuContentData.frameBrowsingContext.currentWindowGlobal.getActor("DLTranslator"); actor.sendAsyncMessage("DLT:CreatePopup", { targetIdentifier, screenX, screenY, fixupX, fixupY, fromLang: null, toLang: null, sourceText: selectionText, }); } }(); this.DLTranslatorParent = class extends JSWindowActorParent { receiveMessage({name, data}) { switch(name) { case "DLT:OpenTranlatorInTab": const win = this.browsingContext.top.embedderElement.ownerGlobal; win.openLinkIn(`https://www.deepl.com/translator#${data.sourceLang}/${data.targetLang}/${encodeURIComponent(data.sourceText)}`, "tab", { relatedToCurrent: true, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), } ); break; } } } } else { Cu.importGlobalProperties(["fetch"]); this.EXPORTED_SYMBOLS = ["DLTranslatorChild"]; this.DLPopupTranslator = class { constructor(win, x, y, sourceText) { this.sourceLang = "EN"; this.targetLang = win.windowGlobalChild.getActor("DLTranslator").defaultLang; this.sourceText = sourceText.trim(); const popup = this.popup = win.document.createElement("div"); Object.assign(popup.style, { position: "absolute", top: `${win.scrollY + y}px`, left: `${win.scrollX + x}px`, width: "400px", maxHeight: "200px", fontFamily: "sans-serif", fontSize: "16px", color: "black", background: "floralwhite", border: "1px solid darkgray", borderRadius: "3px", boxShadow: "3px 3px 5px lightgray", transition: "opacity 0.2s ease", zIndex: "1000", }); const flex = win.document.createElement("div"); Object.assign(flex.style, { display: "flex", maxHeight: "200px", flexDirection: "column", }); const header = win.document.createElement("div"); Object.assign(header.style, { display: "flex", height: "auto", margin: "2px 5px 1px", fontSize: "smaller", alignItems: "center", }); const logo = win.document.createElement("div"); logo.textContent = "DeepL ほんやくくん"; Object.assign(logo.style, { width: "auto", fontWeight: "bold", flexGrow: "1", }); header.appendChild(logo); const langSelector = win.document.createElement("select"); for (let [lang, desc] of Object.entries(supportedLangs)) { const option = win.document.createElement("option"); option.value = lang; option.textContent = desc; langSelector.appendChild(option); } langSelector.value = this.targetLang; Object.assign(langSelector.style, { width: "auto", marginRight: "5px", }); langSelector.addEventListener("change", this); header.appendChild(langSelector); const more = win.document.createElement("div"); more.className = "deepl-translator-more"; more.textContent = "more"; Object.assign(more.style, { width: "auto", cursor: "pointer", }); more.addEventListener("click", this); header.appendChild(more); flex.appendChild(header); const box = win.document.createElement("div"); box.className = "deepl-translator-box"; Object.assign(box.style, { height: "auto", overflow: "auto", background: "white", padding: "2px", margin: "1px 5px 5px", border: "1px solid darkgray", flexGrow: "1", whiteSpace: "pre-wrap", }); flex.appendChild(box); this.popup.appendChild(flex); win.document.body.appendChild(popup); win.setTimeout(() => win.addEventListener("click", this), 0); } handleEvent(event) { const {type, target} = event; switch(type) { case "click": if (!this.popup.contains(target)) { target.ownerGlobal.removeEventListener("click", this); this.popup.addEventListener("transitionend", ({target}) => { target.parentNode.removeChild(target); }, {once:true}); this.popup.style.opacity = 0; } else if (target.className === "deepl-translator-more") { const actor = target.ownerGlobal.windowGlobalChild.getActor("DLTranslator"); actor.sendAsyncMessage("DLT:OpenTranlatorInTab", { sourceText: this.sourceText, sourceLang: this.sourceLang, targetLang: this.targetLang, }); event.stopPropagation(); } break; case "change": this.targetLang = target.value; this.translate(null, this.targetLang); break; } } setText(text, color) { let box = this.popup.querySelector(".deepl-translator-box"); if (color) box.style.color = color; else box.style.color = null; box.textContent = text; } fetchWithPrivilege(url, request) { return new Promise((resolve, reject) => { const {method, headers, body, referrer} = request; let origin = referrer ? referrer : url; const channel = NetUtil.newChannel({ uri: NetUtil.newURI(url), loadingPrincipal: Services.scriptSecurityManager.createContentPrincipal(NetUtil.newURI(origin), {}), contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT || Ci.nsILoadInfo.SEC_REQUIRE_CORS_DATA_INHERITS, }); channel.QueryInterface(Ci.nsIHttpChannel); channel.loadFlags |= Ci.nsIRequest.LOAD_ANONYMOUS; channel.requestMethod = method || "GET"; if (headers) { for (const [name, value] of Object.entries(headers)) { if (channel.setNewReferrerInfo && name.toLowerCase() === "referer") { channel.setNewReferrerInfo( value, Ci.nsIReferrerInfo.UNSAFE_URL, true ); } else { channel.setRequestHeader(name, value, false); } } } if (body) { channel.QueryInterface(Ci.nsIUploadChannel2); const converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter); converter.charset = "UTF-8"; channel.explicitSetUploadStream(converter.convertToInputStream(body), null, -1, method, false); } NetUtil.asyncFetch(channel, (stream, status) => { if (!Components.isSuccessCode(status)) { let e = Components.Exception("", status); reject(new Error(`Error while loading ${url}: ${e.name}`)); } let text = NetUtil.readInputStreamToString(stream, stream.available()); stream.close(); resolve({ ok: channel.requestSucceeded, status: channel.responseStatus, statusText: channel.responseStatusText, contentType: channel.getResponseHeader("content-type"), text }); }); }); } translateUsingBackdoor(from, to) { const getTimestamp = text => { let now = new Date().getTime(); let count = 1; let match = text.match(/i/g); if (match) { count += match.length; } return now + count - now % count; }; this.fetchWithPrivilege("https://www2.deepl.com/jsonrpc", { method: "POST", referrer: "https://www.deepl.com/translator", headers: { "Content-Type": "application/json", Referer: "https://www.deepl.com/translator", }, body: JSON.stringify({ jsonrpc: "2.0", method: "LMT_handle_texts", params: { texts: [{text: this.sourceText}], splitting: "newlines", lang: { target_lang: to.slice(0, 2), source_lang_user_selected: from ? from : "auto", }, timestamp: getTimestamp(this.sourceText), }, id: 1000000+Math.floor(Math.random()*99000000), }, null, 4), }).then(resp => { if (!resp.ok && !resp.contentType.startsWith("application/json")) { throw new Error(`Server returned ${resp.status} ${resp.statusText}`); } return JSON.parse(resp.text); }).then(json => { if (json.error) throw new Error(`Error code ${json.error.code}: ${json.error.message}`); if (!json.result?.texts) throw new Error("Unknown error occurred"); this.setText(json.result.texts[0].text); this.sourceLang = json.result.lang; }).catch(e => { this.setText(e.message, "red"); Cu.reportError(e); }); } translate(from, to) { this.setText("翻訳中...", "lightgray"); if (to) { this.popup.querySelector("select").value = this.targetLang = to; this.popup.ownerGlobal.windowGlobalChild.getActor("DLTranslator").defaultLang = to; } if (!apiKey) return this.translateUsingBackdoor(from, this.targetLang); const body = new FormData(); body.append("text", this.sourceText); body.append("target_lang", this.targetLang); body.append("auth_key", apiKey); if (from) body.append("source_lang", from); return fetch(`${apiEndpoint}/translate`, { method: "POST", referrerPolicy: "no-referrer", credentials: "omit", body, }).then(resp => { if (!resp.ok && resp.status != 400) { throw new Error(`Server returned ${resp.status} ${resp.statusText}`); } return resp.json(); }).then(json => { if (!json.translations) throw new Error(`${json.message}: ${json.detail}`); this.setText(json.translations[0].text); this.sourceLang = json.translations[0].detected_source_language; fetch(`${apiEndpoint}/usage`, { referrerPolicy: "no-referrer", credentials: "omit", headers: { Authorization: `DeepL-Auth-Key ${apiKey}`, }, }).then(resp => { if (!resp.ok) throw new Error(); return resp.json(); }).then(json => { this.popup.title = `Quota: ${(100*json.character_count/json.character_limit).toPrecision(2)}% (${json.character_count} / ${json.character_limit})`; }).catch(()=>{}); }).catch(e => { this.setText(e.message, "red"); Cu.reportError(e); }); } } this.DLTranslatorChild = class extends JSWindowActorChild { actorCreated() { this.defaultLang = defaultLang; this.keyRepeat = 0; } createPopupWithScreenCoordinate(screenX, screenY, sourceText) { let x = screenX - this.contentWindow.screenX - this.contentWindow.outerWidth + this.contentWindow.innerWidth; let y = screenY - this.contentWindow.screenY - this.contentWindow.outerHeight + this.contentWindow.innerHeight; return this.createPopupWithClientCoordinate(x, y, sourceText); } createPopupWithClientCoordinate(clientX, clientY, sourceText) { let x = clientX; let y = clientY; let clientWidth = this.contentWindow.document.documentElement.clientWidth; let clientHeight = this.contentWindow.document.documentElement.clientHeight; if (x + 400 > clientWidth) x = clientWidth - 400; if (y + 200 > clientHeight) y = clientHeight - 200; x = Math.max(x, 0); y = Math.max(y, 0); return new DLPopupTranslator(this.contentWindow, x, y, sourceText); } createPopupWithSelection() { const selection = this.contentWindow.getSelection(); const text = selection.toString().trim(); if (text) { let rect = selection.getRangeAt(0).getBoundingClientRect(); return this.createPopupWithClientCoordinate(rect.left, rect.top+rect.height, text); } return null; } receiveMessage({name, data}) { switch(name) { case "DLT:CreatePopup": let fixupX = 0; let fixupY = 0; if (data.fixupX) fixupX = data.fixupX; if (data.fixupY) fixupY = data.fixupY; this.createPopupWithScreenCoordinate(data.screenX+fixupX, data.screenY+fixupY, data.sourceText).translate(data.fromLang, data.toLang); break; case "DLT:CreatePopupWithClientCoordinate": this.createPopupWithClientCoordinate(data.clientX, data.clientY, data.sourceText).translate(data.fromLang, data.toLang); break; } } handleEvent(event) { switch (event.type) { case "keyup": if (event.code === hotkey.code && this.contentWindow.getSelection()?.toString()) { if (!this.keyRepeat) { new Promise((resolve, reject) => { this.hotkeyResolver = resolve; this.hotkeyRejector = reject; this.contentWindow.setTimeout(() => reject(), hotkey.timeout); }).then(() => { this.keyRepeat = 0; this.hotkeyResolver = null; this.hotkeyRejector = null; this.createPopupWithSelection()?.translate(null, this.defaultLang); }).catch(() => { this.keyRepeat = 0; this.hotkeyResolver = null; this.hotkeyRejector = null; }); } if (++this.keyRepeat === hotkey.repeat) { this.hotkeyResolver(); } } else if (this.keyRepeat) { this.hotkeyRejector(); } break; } } } } } else { try { if (parseInt(Services.appinfo.version) < 101) { ChromeUtils.import(Components.stack.filename).DLTranslator.attachToWindow(window); } else { const fileHandler = Services.io.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler); const scriptFile = fileHandler.getFileFromURLSpec(Components.stack.filename); const resourceHandler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler); if (!resourceHandler.hasSubstitution("deepl-ucjs")) { resourceHandler.setSubstitution("deepl-ucjs", Services.io.newFileURI(scriptFile.parent)); } ChromeUtils.import(`resource://deepl-ucjs/${encodeURIComponent(scriptFile.leafName)}?${scriptFile.lastModifiedTime}`).DLTranslator.attachToWindow(window); } } catch(e) {} }
Without apikey, the script would translate text through backdoor, there will be some restrictions.
Due to Deepl unsuitable Chinese service, I modified the script to use Baidu translation API
// ==UserScript==// @name Baidu Translator// @author Ryan, BSTweaker// @include main// @compatibility Firefox 78+// @homepageURL https://github.com/benzBrake/FirefoxCustomize/tree/master/userChromeJS// @description 在上下文菜单中添加使用百度翻译所选文本的功能// @note 从 DLTranslator (https://bitbucket.org/BSTweaker/userchromejs/src/master/DeepLTranslator.uc.js)修改而来// @charset UTF-8// ==/UserScript==const BDT_OPTIONS = { defaultLang: "zh", enableContextMenu: true, hotkey: { enabled: true, code: "AltLeft", repeat: 2, timeout: 500, }, supportedLangs: { //"ara": "Arabic", //"bul": "Bulgarian", //"cs": "Czech", //"dan": "Danish", "de": "German", //"el": "Greek", "en": "English", //"est": "Estonian", //"fin": "Finnish", //"fra": "French", //"hu": "Hungarian", //"it": "Italian", //"jp": "Japanese", //"lit": "Lithuanian", //"lav": "Latvian", //"nl": "Dutch", //"pl": "Polish", "pt": "Portuguese", "pot": "Portuguese (Brazilian)", //"rom": "Romanian", //"ru": "Russian", //"sk": "Slovak", //"slo": "Slovenian", "spa": "Spanish", //"swe": "Swedish", "th": "Thai", "vie": "Vietnamese", "zh": "简体中文", "cht": "繁體中文", "wyw": "文言文", }}if (typeof window === "undefined" || globalThis !== window) { const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); ChromeUtils.defineModuleGetter(this, "ContentDOMReference", "resource://gre/modules/ContentDOMReference.jsm"); ChromeUtils.defineModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); if (!Services.appinfo.remoteType) { this.EXPORTED_SYMBOLS = ["BDTranslator", "BDTranslatorParent"]; try { const actorParams = { parent: { moduleURI: __URI__, }, child: { moduleURI: __URI__, events: {}, }, allFrames: true, messageManagerGroups: ["browsers"], matches: [`*://*/*`], }; if (BDT_OPTIONS.hotkey.enabled) { if (BDT_OPTIONS.hotkey.repeat <= 0) BDT_OPTIONS.hotkey.repeat = 2; if (BDT_OPTIONS.hotkey.timeout <= 0) BDT_OPTIONS.hotkey.timeout = 500; actorParams.child.events.keyup = {}; } ChromeUtils.registerWindowActor("BDTranslator", actorParams); } catch (e) { Cu.reportError(e); } this.BDTranslatorParent = class extends JSWindowActorParent { receiveMessage({ name, data }) { // https://searchfox.org/mozilla-central/rev/43ee5e789b079e94837a21336e9ce2420658fd19/browser/actors/ContextMenuParent.sys.mjs#60-63 let browser = this.browsingContext.top.embedderElement; let win = browser.ownerGlobal; const { BDTranslator } = win; switch (name) { case "BDT:OpenTranlatorInTab": BDTranslator.translateTextInNewTab(data.sourceText, data.from, data.to); break; case "BDT:TranslateText": BDTranslator.translateText(data.sourceText, data.from, data.to).then(result => { this.sendAsyncMessage("BDT:TranslateReulult", { ...data, resultObject: result }) }) break; } } } } else { Cu.importGlobalProperties(["fetch"]); this.EXPORTED_SYMBOLS = ["BDTranslatorChild"]; const BDPopupTranslator = { popup: null, show(win, x, y, sourceText, resultObject) { const options = BDT_OPTIONS; if (resultObject && "trans_result" in resultObject) { this.fromLang = resultObject["trans_result"].from || "auto"; this.targetLang = resultObject["trans_result"].to || options.defaultLang } else { this.fromLang = "auto"; this.targetLang = options.defaultLang; } this.sourceText = sourceText.trim(); if (!win.document.getElementById("baidu-translator")) { this.popup = win.document.createElement("div"); this.popup.id = "baidu-translator" Object.assign(this.popup.style, { position: "absolute", top: `${win.scrollY + y}px`, left: `${win.scrollX + x}px`, width: "400px", maxHeight: "200px", fontFamily: "sans-serif", fontSize: "16px", color: "black", background: "floralwhite", border: "1px solid darkgray", borderRadius: "3px", boxShadow: "3px 3px 5px lightgray", transition: "opacity 0.2s ease", zIndex: "1000", }); const flex = win.document.createElement("div"); Object.assign(flex.style, { display: "flex", maxHeight: "200px", flexDirection: "column", }); const header = win.document.createElement("div"); Object.assign(header.style, { display: "flex", height: "auto", margin: "2px 5px 1px", fontSize: "smaller", alignItems: "center", }); const logo = win.document.createElement("div"); logo.textContent = "翻译结果"; Object.assign(logo.style, { width: "auto", fontWeight: "bold", flexGrow: "1", }); header.appendChild(logo); const langSelector = win.document.createElement("select"); for (let [lang, desc] of Object.entries(options.supportedLangs)) { const option = win.document.createElement("option"); option.value = lang; option.textContent = desc; langSelector.appendChild(option); } langSelector.value = this.targetLang; Object.assign(langSelector.style, { width: "auto", marginRight: "5px", }); langSelector.addEventListener("change", this); header.appendChild(langSelector); const more = win.document.createElement("div"); more.className = "baidu-translator-more"; more.textContent = "更多"; Object.assign(more.style, { width: "auto", cursor: "pointer", }); more.addEventListener("click", this); header.appendChild(more); flex.appendChild(header); const box = win.document.createElement("div"); box.className = "baidu-translator-box"; Object.assign(box.style, { height: "auto", overflow: "auto", background: "white", padding: "2px", margin: "1px 5px 5px", border: "1px solid darkgray", flexGrow: "1", whiteSpace: "pre-wrap", }); flex.appendChild(box); this.popup.appendChild(flex); win.document.body.appendChild(this.popup); win.setTimeout(() => win.addEventListener("click", this), 0); } else { this.setPos(x, y); } if (resultObject && "trans_result" in resultObject) { let data = resultObject.trans_result.data; this.setText(data.map(el => el.dst).join("\n")); } }, handleEvent(event) { const { type, target } = event; const actor = target.ownerGlobal.windowGlobalChild.getActor("BDTranslator"); switch (type) { case "click": if (!this.popup.contains(target)) { target.ownerGlobal.removeEventListener("click", this); this.popup.addEventListener("transitionend", ({ target }) => { target.parentNode.removeChild(target); }, { once: true }); this.popup.style.opacity = 0; } else if (target.className === "baidu-translator-more") { actor.sendAsyncMessage("BDT:OpenTranlatorInTab", { sourceText: this.sourceText, from: this.fromLang, to: this.targetLang, }); event.stopPropagation(); } break; case "change": this.targetLang = target.value; this.translate(actor, this.sourceText, this.fromLang, this.targetLang); break; } }, setPos(x, y) { Object.assign(this.popup.style, { top: `${win.scrollY + y}px`, left: `${win.scrollX + x}px`, opacity: 1 }); }, setText(text, color) { let box = this.popup.querySelector(".baidu-translator-box"); if (color) box.style.color = color; else box.style.color = null; box.textContent = text; }, translate(actor, text, from, to) { this.setText("正在翻译中...", "lightgray"); actor.sendAsyncMessage("BDT:TranslateText", { sourceText: text, from: from || this.fromLang, to: to || this.targetLang }); } } this.BDTranslatorChild = class extends JSWindowActorChild { actorCreated() { this.keyRepeat = 0; } createPopupWithScreenCoordinate(screenX, screenY, sourceText, resultObject) { let x = screenX - this.contentWindow.screenX - this.contentWindow.outerWidth + this.contentWindow.innerWidth; let y = screenY - this.contentWindow.screenY - this.contentWindow.outerHeight + this.contentWindow.innerHeight; this.createPopupWithClientCoordinate(x, y, sourceText, resultObject); } createPopupWithClientCoordinate(clientX, clientY, sourceText, resultObject) { let x = clientX; let y = clientY; let clientWidth = this.contentWindow.document.documentElement.clientWidth; let clientHeight = this.contentWindow.document.documentElement.clientHeight; if (x + 400 > clientWidth) x = clientWidth - 400; if (y + 200 > clientHeight) y = clientHeight - 200; x = Math.max(x, 0); y = Math.max(y, 0); if (resultObject) { BDPopupTranslator.show(this.contentWindow, x, y, sourceText, resultObject); } else { BDPopupTranslator.show(this.contentWindow, x, y, sourceText); BDPopupTranslator.translate(this.contentWindow.windowGlobalChild.getActor("BDTranslator"), sourceText); } } createPopupWithSelection() { const selection = this.contentWindow.getSelection(); const text = selection.toString().trim(); if (text) { let rect = selection.getRangeAt(0).getBoundingClientRect(); return this.createPopupWithClientCoordinate(rect.left, rect.top + rect.height, text); } return null; } receiveMessage({ name, data }) { switch (name) { case "BDT:CreatePopup": let fixupX = 0; let fixupY = 0; if (data.fixupX) fixupX = data.fixupX; if (data.fixupY) fixupY = data.fixupY; this.createPopupWithScreenCoordinate(data.screenX + fixupX, data.screenY + fixupY, data.sourceText, data.resultObject); break; case "BDT:TranslateReulult": let resultObject = data.resultObject; if (resultObject && "trans_result" in resultObject) { let data = resultObject.trans_result.data; BDPopupTranslator.setText(data.map(el => el.dst).join("\n")); } else { BDPopupTranslator.setText("翻译失败!", "red"); } break; case "BDT:CreatePopupWithClientCoordinate": this.createPopupWithClientCoordinate(data.clientX, data.clientY, data.sourceText).translate(data.fromLang, data.toLang); break; } } handleEvent(event) { switch (event.type) { case "keyup": if (event.code === BDT_OPTIONS.hotkey.code && this.contentWindow.getSelection()?.toString()) { if (!this.keyRepeat) { new Promise((resolve, reject) => { this.hotkeyResolver = resolve; this.hotkeyRejector = reject; this.contentWindow.setTimeout(() => reject(), BDT_OPTIONS.hotkey.timeout); }).then(() => { this.keyRepeat = 0; this.hotkeyResolver = null; this.hotkeyRejector = null; this.createPopupWithSelection(); }).catch(() => { this.keyRepeat = 0; this.hotkeyResolver = null; this.hotkeyRejector = null; }); } if (++this.keyRepeat === BDT_OPTIONS.hotkey.repeat) { this.hotkeyResolver(); } } else if (this.keyRepeat) { this.hotkeyRejector(); } break; } } } }} else { try { if (parseInt(Services.appinfo.version) < 101) { ChromeUtils.import(Components.stack.filename); } else { let fileHandler = Services.io.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler); let scriptPath = Components.stack.filename; if (scriptPath.startsWith("chrome")) { scriptPath = resolveChromeURL(scriptPath); function resolveChromeURL(str) { const registry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry); try { return registry.convertChromeURL(Services.io.newURI(str.replace(/\\/g, "/"))).spec } catch (e) { console.error(e); return "" } } } let scriptFile = fileHandler.getFileFromURLSpec(scriptPath); let resourceHandler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler); if (!resourceHandler.hasSubstitution("bdt-ucjs")) { resourceHandler.setSubstitution("bdt-ucjs", Services.io.newFileURI(scriptFile.parent)); } ChromeUtils.import(`resource://bdt-ucjs/${encodeURIComponent(scriptFile.leafName)}?${scriptFile.lastModifiedTime}`); } } catch (e) { console.error(e) } (function () { window.BDTranslator = { get appVersion() { delete this.appVersion; return this.appVersion = parseFloat(Services.appinfo.version); }, init: async function () { window.addEventListener('unload', this, false); /** * 获取必备头 */ let respText = await (await fetch("https://fanyi.baidu.com")).text(); this.gtk = /window\.gtk = ('|")(.*?)('|")/.exec(respText)[2]; this.token = /token: ('|")(.*?)('|")/.exec(respText)[2]; if (BDT_OPTIONS.enableContextMenu) this.addContextMenuitem(); }, /** * 添加右键菜单 */ addContextMenuitem() { const menuitem = $C("menuitem", { id: 'menu-translate-selected', label: "翻译选中文本" }); menuitem.addEventListener('command', this, false); this.menuitem = $('contentAreaContextMenu').appendChild(menuitem); $('contentAreaContextMenu').addEventListener('popupshowing', this); }, /** * 通过 API 获取语言 * @param {string} text 待检测文本 * @returns */ checkLang: async function (text) { const rawText = text.replace(/[\uD800-\uDBFF]$/, "").slice(0, 50); const data = new URLSearchParams(); data.append('query', rawText); const options = { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: data, }; try { const response = await fetch('https://fanyi.baidu.com/langdetect', options); const { lan } = await response.json(); return lan; } catch (error) { console.log(error); return; } }, /** * 网页翻译接口 * * @param {string} text 带翻译文本 * @param {string} from 源语言,不提供此参数则自动检测 * @param {string} to 目标语言,不提供则默认翻译为默认语言 * @returns */ translateText: async function (text, from, to) { if (!from) { from = await this.checkLang(text); } to || (to = BDT_OPTIONS.defaultLang); const processedText = text.length > 30 ? (text.substring(0, 10) + text.substring(~~(text.length / 2) - 5, ~~(text.length / 2) + 5) + text.substring(text.length - 10)) : text; const data = new URLSearchParams(); data.append('from', from); data.append('to', to); data.append('query', text); data.append('simple_means_flag', '3'); data.append('sign', calcTk(processedText, this.gtk)); data.append('token', this.token); data.append('domain', 'common'); const options = { method: 'POST', headers: { 'referer': 'https://fanyi.baidu.com', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', }, body: data, }; try { const response = await fetch('https://fanyi.baidu.com/v2transapi', options); const res = await response.json(); return res; // Process the response as needed } catch (error) { console.log(error); return; } }, /** * 打开翻译网页 * @param {string} text 待翻译文本 * @param {string} from 源语言 * @param {string} to 目标语言 */ translateTextInNewTab: function (text, from, to) { const urlTemplate = "https://fanyi.baidu.com/#{sourceLang}/{targetLang}/{sourceText}"; const url = urlTemplate.replace("{sourceLang}", from).replace("{targetLang}", to).replace("{sourceText}", text) if (this.appVersion < 78) { openUILinkIn(url, 'tab', false, null); } else { openWebLinkIn(url, 'tab', { postData: null, triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({ userContextId: gBrowser.selectedBrowser.getAttribute( "userContextId" ) }) }); } }, /** * 事件处理 * @param {*} event */ handleEvent: function (event) { switch (event.type) { case "unload": this.uninit(); break; case "command": this.beginTranslate(event.target.ownerGlobal.gContextMenu?.contentData); break; case "popupshowing": this.popupshowing(event.target.ownerGlobal); break; } }, /** * 点击菜单开始翻译 * * @param {*} contextMenuContentData * @returns */ beginTranslate: async function (contextMenuContentData) { if (!contextMenuContentData) return; const win = contextMenuContentData.browser.ownerGlobal; const selectionText = contextMenuContentData.selectionInfo?.fullText; const targetIdentifier = contextMenuContentData.context?.targetIdentifier; const screenX = contextMenuContentData.context?.screenX ?? contextMenuContentData.context?.screenXDevPx / win.devicePixelRatio; const screenY = contextMenuContentData.context?.screenY ?? contextMenuContentData.context?.screenYDevPx / win.devicePixelRatio; const browser = contextMenuContentData.browser; const browserBoundingRect = browser.getBoundingClientRect(); const fixupX = browser.ownerGlobal.outerWidth - browserBoundingRect.left - browserBoundingRect.width; const fixupY = 20 + browser.ownerGlobal.outerHeight - browserBoundingRect.top - browserBoundingRect.height; const actor = contextMenuContentData.frameBrowsingContext.currentWindowGlobal.getActor("BDTranslator"); let translatedResult = await this.translateText(selectionText); actor.sendAsyncMessage("BDT:CreatePopup", { targetIdentifier, screenX, screenY, fixupX, fixupY, sourceText: selectionText, resultObject: translatedResult }); }, /** * 没有选中文本的时候隐藏右键菜单 * @param {ChromeWindow} win */ popupshowing(win) { let selectionText = win.gContextMenu?.contentData?.selectionInfo?.text; this.menuitem.hidden = !selectionText; }, uninit: async function () { window.removeEventListener('unload', this, false); $('contentAreaContextMenu').removeEventListener('popupshowing', this); if (this.menuitem) this.menuitem.parentNode.removeChild(this.menuitem); delete window.BDTranslator; } } function $(id) { return document.getElementById(id); } function $C(tag, attrs, skipAttrs) { var el; if (!tag) return el; attrs = attrs || {}; skipAttrs = skipAttrs || []; if (tag.startsWith('html:')) el = document.createElement(tag); else el = document.createXULElement(tag); return $A(el, attrs, skipAttrs); } function $A(el, attrs, skipAttrs) { skipAttrs = skipAttrs || []; if (attrs) Object.keys(attrs).forEach(function (key) { if (!skipAttrs.includes(key)) { if (typeof attrs[key] === 'function') el.setAttribute(key, "(" + attrs[key].toString() + ").call(this, event);"); else el.setAttribute(key, attrs[key]); } }); return el; } /** 签名计算 */ function calcTk(a, b) { var d = b.split("."); b = Number(d[0]) || 0; for (var e = [], f = 0, g = 0; g < a.length; g++) { var k = a.charCodeAt(g); 128 > k ? e[f++] = k : (2048 > k ? e[f++] = k >> 6 | 192 : (55296 == (k & 64512) && g + 1 < a.length && 56320 == (a.charCodeAt(g + 1) & 64512) ? (k = 65536 + ((k & 1023) << 10) + (a.charCodeAt(++g) & 1023), e[f++] = k >> 18 | 240, e[f++] = k >> 12 & 63 | 128) : e[f++] = k >> 12 | 224, e[f++] = k >> 6 & 63 | 128), e[f++] = k & 63 | 128) } a = b; for (f = 0; f < e.length; f++)a = Fo(a + e[f], "+-a^+6"); a = Fo(a, "+-3^+b+-f"); a ^= Number(d[1]) || 0; 0 > a && (a = (a & 2147483647) + 2147483648); a %= 1E6; return a.toString() + "." + (a ^ b) } function Fo(a, b) { for (var c = 0; c < b.length - 2; c += 3) { var d = b.charAt(c + 2); d = "a" <= d ? d.charCodeAt(0) - 87 : Number(d); d = "+" == b.charAt(c + 1) ? a >>> d : a << d; a = "+" == b.charAt(c) ? a + d & 4294967295 : a ^ d } return a } window.BDTranslator.init(); })()}
You can try this userchrome.js script
// ==UserScript==
// @name AutoCopySelectionText.uc.js
// @description 自动复制选中文本(ScrLk 亮起时不复制)
// @author Ryan
// @version 2022.07.28
// @compatibility Firefox 87
// @charset UTF-8
// @system windows
// @license MIT License
// @include main
// @shutdown window.AutoCopySelectionText.destroy();
// @homepageURL https://github.com/benzBrake/FirefoxCustomize/tree/master/userChromeJS
// @version 2022.07.28 网页支持文本框
// @version 2022.07.18 支持长按延时
// @version 2022.07.16 重写代码,支持热插拔,采用 异步消息,支持 Firefox 内置页面
// @version 2022.07.13 初始化版本
// ==/UserScript==
(function () {
class AutoCopySelectionText {
constructor() {
Components.utils.import("resource://gre/modules/ctypes.jsm");
// will be transfered to control by toolbar button
let user32 = ctypes.open("user32.dll");
this.getKeyState = user32.declare('GetKeyState', ctypes.winapi_abi, ctypes.bool, ctypes.int);
function frameScript() {
const { Services } = Components.utils.import(
"resource://gre/modules/Services.jsm"
);
// implement read from about:config preferences in future
var WAIT_TIME = 0; // Change it to any number as you want
var TRIM_SELECTION = true; // remove spaces before and after the string
// Do not modify below ------------------------------------------
var LONG_PRESS = false;
var TIMEOUT_ID = null;
function handleEvent(event) {
if (event.button !== 0) return; // only trigger when left button up
if (TIMEOUT_ID)
content.clearTimeout(TIMEOUT_ID);
const focusedElement =
Services.focus.focusedElement ||
event.originalTarget.ownerDocument?.activeElement;
switch (event.type) {
case 'mousemove':
TIMEOUT_ID = content.setTimeout(function () {
LONG_PRESS = true;
}, WAIT_TIME);
case 'mouseup':
// copy text on mouse button up
if (LONG_PRESS) {
let data = { text: getSelection(content, focusedElement) }
sendSyncMessage("acst_selectionData", data);
}
break;
}
LONG_PRESS = false;
}
// From addMenuPlus.uc.js
function getSelection(win, focusedElement) {
win || (win = content);
var selection = win.getSelection().toString();
if (!selection) {
let element = focusedElement;
let isOnTextInput = function (elem) {
return elem instanceof HTMLTextAreaElement ||
(elem instanceof HTMLInputElement && elem.mozIsTextField(true));
};
if (isOnTextInput(element)) {
selection = element.value.substring(element.selectionStart,
element.selectionEnd);
}
}
if (TRIM_SELECTION && selection) {
selection = selection.replace(/^\s+/, "")
.replace(/\s+$/, "")
.replace(/\s+/g, " ");
}
return selection;
}
["mousemove", "mouseup"].forEach((t) => addEventListener(t, handleEvent, false));
function receiveMessage(message) {
switch (message.name) {
case 'acst_destroy':
["mousemove", "mouseup"].forEach((t) => removeEventListener(t, handleEvent, false));
removeMessageListener("acst_destroy", receiveMessage);
handleEvent = null;
receiveMessage = null;
break;
}
}
addMessageListener("acst_destroy", receiveMessage);
}
let frameScriptURI = 'data:application/javascript,'
+ encodeURIComponent('(' + frameScript.toString() + ')()');
window.messageManager.loadFrameScript(frameScriptURI, true);
window.messageManager.addMessageListener("acst_selectionData", this);
}
receiveMessage(message) {
switch (message.name) {
case 'acst_selectionData':
if (this.getKeyState(0x91)) return;
if (message.data.text)
Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper).copyString(message.data.text);
break;
}
}
destroy() {
window.messageManager.broadcastAsyncMessage("acst_destroy");
window.messageManager.removeMessageListener("acst_selectionData", this);
delete window.AutoCopySelectionText;
}
}
window.AutoCopySelectionText = new AutoCopySelectionText();
})()
Alles anzeigen
You can use this firefox loader to run portable firefox
This loader allow to set firefox as default browser in firefox preferences directly
Das würde so nicht funktionieren, der → // 1, 2, 3, 4, 5 Teil verhindert, das das CSS-Schnipsel funktioniert, das müsste als angehängter Kommentar so /* 1, 2, 3, 4, 5 */ aussehen.
Sorry, that was my oversight