Insert code into html only when translating text

This commit is contained in:
sienori 2019-10-20 17:26:48 +09:00
parent 6fb3a79767
commit c78a27a7e0
2 changed files with 154 additions and 166 deletions

View file

@ -1,51 +1,11 @@
import React, { Component } from "react"; import React, { Component } from "react";
import browser from "webextension-polyfill"; import browser from "webextension-polyfill";
import translate from "src/common/translate"; import translate from "src/common/translate";
import { initSettings, getSettings, handleSettingsChange } from "src/settings/settings"; import { getSettings } from "src/settings/settings";
import { updateLogLevel, overWriteLogLevel } from "src/common/log";
import TranslateButton from "./TranslateButton"; import TranslateButton from "./TranslateButton";
import TranslatePanel from "./TranslatePanel"; import TranslatePanel from "./TranslatePanel";
import "../styles/TranslateContainer.scss"; import "../styles/TranslateContainer.scss";
const getSelectedText = () => {
const element = document.activeElement;
const isInTextField = element.tagName === "INPUT" || element.tagName === "TEXTAREA";
const selectedText = isInTextField
? element.value.substring(element.selectionStart, element.selectionEnd)
: window.getSelection().toString();
return selectedText;
};
const getSelectedPosition = () => {
const element = document.activeElement;
const isInTextField = element.tagName === "INPUT" || element.tagName === "TEXTAREA";
const selectedRect = isInTextField
? element.getBoundingClientRect()
: window
.getSelection()
.getRangeAt(0)
.getBoundingClientRect();
let selectedPosition;
const panelReferencePoint = getSettings("panelReferencePoint");
switch (panelReferencePoint) {
case "topSelectedText":
selectedPosition = {
x: selectedRect.left + selectedRect.width / 2,
y: selectedRect.top
};
break;
case "bottomSelectedText":
default:
selectedPosition = {
x: selectedRect.left + selectedRect.width / 2,
y: selectedRect.bottom
};
break;
}
return selectedPosition;
};
const translateText = async (text, targetLang = getSettings("targetLang")) => { const translateText = async (text, targetLang = getSettings("targetLang")) => {
const result = await translate(text, "auto", targetLang); const result = await translate(text, "auto", targetLang);
return result; return result;
@ -71,15 +31,10 @@ const matchesTargetLang = async selectedText => {
return matchsLangs; return matchsLangs;
}; };
const waitTime = time => {
return new Promise(resolve => setTimeout(() => resolve(), time));
};
export default class TranslateContainer extends Component { export default class TranslateContainer extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
isInit: false,
shouldShowButton: false, shouldShowButton: false,
buttonPosition: { x: 0, y: 0 }, buttonPosition: { x: 0, y: 0 },
shouldShowPanel: false, shouldShowPanel: false,
@ -88,75 +43,26 @@ export default class TranslateContainer extends Component {
candidateText: "", candidateText: "",
statusText: "OK" statusText: "OK"
}; };
this.selectedText = ""; this.selectedText = props.selectedText;
this.selectedPosition = { x: 0, y: 0 }; this.selectedPosition = props.selectedPosition;
this.init();
} }
init = async () => { componentDidMount = () => {
await initSettings(); if (this.props.shouldTranslate) this.showPanel();
this.setState({ isInit: true }); else this.handleTextSelect(this.props.clickedPosition);
document.addEventListener("mouseup", this.handleMouseUp);
document.addEventListener("keydown", this.handleKeyDown);
browser.storage.onChanged.addListener(handleSettingsChange);
browser.runtime.onMessage.addListener(this.handleMessage);
overWriteLogLevel();
updateLogLevel();
if (this.props.isFirst) this.disableExtensionByUrlList();
}; };
disableExtensionByUrlList = () => { handleTextSelect = async clickedPosition => {
const disableUrls = getSettings("disableUrlList").split("\n"); const onSelectBehavior = getSettings("whenSelectText");
let pageUrl; if (onSelectBehavior === "dontShowButton") return this.props.removeContainer();
try {
pageUrl = top.location.href; if (getSettings("ifCheckLang")) {
} catch (e) { const matchesLang = await matchesTargetLang(this.selectedText);
pageUrl = document.referrer; if (matchesLang) return this.props.removeContainer();
} }
const matchesPageUrl = urlPattern => { if (onSelectBehavior === "showButton") this.showButton(clickedPosition);
const pattern = urlPattern else if (onSelectBehavior === "showPanel") this.showPanel(clickedPosition);
.trim()
.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, match => (match === "*" ? ".*" : "\\" + match));
if (pattern === "") return false;
return RegExp("^" + pattern + "$").test(pageUrl);
};
const isMatched = disableUrls.some(matchesPageUrl);
if (isMatched) this.props.removeElement();
};
handleMessage = async request => {
const empty = new Promise(resolve => {
setTimeout(() => {
return resolve("");
}, 100);
});
switch (request.message) {
case "getTabUrl":
if (window == window.parent) return location.href;
else return empty;
case "getSelectedText":
if (this.selectedText.length === 0) return empty;
else return this.selectedText;
case "translateSelectedText":
this.selectedText = getSelectedText();
if (this.selectedText.length === 0) return;
this.selectedPosition = getSelectedPosition();
this.hideButton();
this.showPanel();
break;
default:
return empty;
}
};
handleKeyDown = e => {
if (e.key === "Escape") {
this.hideButton();
this.hidePanel();
}
}; };
showButton = clickedPosition => { showButton = clickedPosition => {
@ -200,50 +106,7 @@ export default class TranslateContainer extends Component {
this.setState({ shouldShowPanel: false }); this.setState({ shouldShowPanel: false });
}; };
handleMouseUp = async e => {
await waitTime(0);
const isLeftClick = e.button === 0;
const isInPasswordField = e.target.tagName === "INPUT" && e.target.type === "password";
const isInThisElement = document.querySelector("#simple-translate").contains(e.target);
if (!isLeftClick) return;
if (isInPasswordField) return;
if (isInThisElement) return;
this.hideButton();
this.hidePanel();
this.selectedText = getSelectedText();
this.selectedPosition = getSelectedPosition();
const clickedPosition = { x: e.clientX, y: e.clientY };
if (this.selectedText.length === 0) return;
this.handleTextSelect(clickedPosition);
};
handleTextSelect = async clickedPosition => {
const onSelectBehavior = getSettings("whenSelectText");
if (onSelectBehavior === "dontShowButton") return;
if (getSettings("ifCheckLang")) {
const matchesLang = await matchesTargetLang(this.selectedText);
if (matchesLang) return;
}
if (onSelectBehavior === "showButton") {
this.showButton(clickedPosition);
} else if (onSelectBehavior === "showPanel") {
this.showPanel(clickedPosition);
}
};
componentWillUnmount() {
document.removeEventListener("mouseup", this.handleMouseUp);
document.removeEventListener("keydown", this.handleKeyDown);
browser.storage.onChanged.removeListener(handleSettingsChange);
browser.runtime.onMessage.removeListener(this.handleMessage);
}
render = () => { render = () => {
if (!this.state.isInit) return null;
return ( return (
<div> <div>
<TranslateButton <TranslateButton

View file

@ -1,8 +1,91 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import browser from "webextension-polyfill"; import browser from "webextension-polyfill";
import { initSettings, getSettings, handleSettingsChange } from "src/settings/settings";
import { updateLogLevel, overWriteLogLevel } from "src/common/log";
import TranslateContainer from "./components/TranslateContainer"; import TranslateContainer from "./components/TranslateContainer";
const init = async () => {
await initSettings();
document.addEventListener("mouseup", handleMouseUp);
document.addEventListener("keydown", handleKeyDown);
browser.storage.onChanged.addListener(handleSettingsChange);
browser.runtime.onMessage.addListener(handleMessage);
overWriteLogLevel();
updateLogLevel();
disableExtensionByUrlList();
};
init();
const handleMouseUp = async e => {
await waitTime(10);
const isLeftClick = e.button === 0;
const isInPasswordField = e.target.tagName === "INPUT" && e.target.type === "password";
const isInThisElement =
document.querySelector("#simple-translate") &&
document.querySelector("#simple-translate").contains(e.target);
if (!isLeftClick) return;
if (isInPasswordField) return;
if (isInThisElement) return;
removeTranslatecontainer();
const selectedText = getSelectedText();
if (selectedText.length === 0) return;
const clickedPosition = { x: e.clientX, y: e.clientY };
const selectedPosition = getSelectedPosition();
showTranslateContainer(selectedText, selectedPosition, clickedPosition);
};
const waitTime = time => {
return new Promise(resolve => setTimeout(() => resolve(), time));
};
const getSelectedText = () => {
const element = document.activeElement;
const isInTextField = element.tagName === "INPUT" || element.tagName === "TEXTAREA";
const selectedText = isInTextField
? element.value.substring(element.selectionStart, element.selectionEnd)
: window.getSelection().toString();
return selectedText;
};
const getSelectedPosition = () => {
const element = document.activeElement;
const isInTextField = element.tagName === "INPUT" || element.tagName === "TEXTAREA";
const selectedRect = isInTextField
? element.getBoundingClientRect()
: window
.getSelection()
.getRangeAt(0)
.getBoundingClientRect();
let selectedPosition;
const panelReferencePoint = getSettings("panelReferencePoint");
switch (panelReferencePoint) {
case "topSelectedText":
selectedPosition = {
x: selectedRect.left + selectedRect.width / 2,
y: selectedRect.top
};
break;
case "bottomSelectedText":
default:
selectedPosition = {
x: selectedRect.left + selectedRect.width / 2,
y: selectedRect.bottom
};
break;
}
return selectedPosition;
};
const handleKeyDown = e => {
if (e.key === "Escape") {
removeTranslatecontainer();
}
};
let isEnabled = true; let isEnabled = true;
const handleMessage = async request => { const handleMessage = async request => {
const empty = new Promise(resolve => { const empty = new Promise(resolve => {
@ -12,45 +95,87 @@ const handleMessage = async request => {
}); });
switch (request.message) { switch (request.message) {
case "getTabUrl":
if (!isEnabled) return empty;
if (window == window.parent) return location.href;
else return empty;
case "getSelectedText": {
if (!isEnabled) return empty;
const selectedText = getSelectedText();
if (selectedText.length === 0) return empty;
else return selectedText;
}
case "translateSelectedText": {
if (!isEnabled) return empty;
const selectedText = getSelectedText();
if (selectedText.length === 0) return;
const selectedPosition = getSelectedPosition();
removeTranslatecontainer();
showTranslateContainer(selectedText, selectedPosition, null, true);
break;
}
case "getEnabled": case "getEnabled":
return isEnabled; return isEnabled;
case "enableExtension": case "enableExtension":
insertElement(); isEnabled = true;
break; break;
case "disableExtension": case "disableExtension":
removeElement(); removeTranslatecontainer();
isEnabled = false;
break; break;
default: default:
return empty; return empty;
} }
}; };
browser.runtime.onMessage.addListener(handleMessage);
const removeElement = () => { const disableExtensionByUrlList = () => {
const disableUrls = getSettings("disableUrlList").split("\n");
let pageUrl;
try {
pageUrl = top.location.href;
} catch (e) {
pageUrl = document.referrer;
}
const matchesPageUrl = urlPattern => {
const pattern = urlPattern
.trim()
.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, match => (match === "*" ? ".*" : "\\" + match));
if (pattern === "") return false;
return RegExp("^" + pattern + "$").test(pageUrl);
};
const isMatched = disableUrls.some(matchesPageUrl);
if (isMatched) isEnabled = false;
};
const removeTranslatecontainer = async () => {
const element = document.getElementById("simple-translate"); const element = document.getElementById("simple-translate");
if (!element) return; if (!element) return;
ReactDOM.unmountComponentAtNode(element); ReactDOM.unmountComponentAtNode(element);
element.parentNode.removeChild(element); element.parentNode.removeChild(element);
isEnabled = false;
}; };
let isFirst = true; const showTranslateContainer = (
const insertElement = () => { selectedText,
selectedPosition,
clickedPosition = null,
shouldTranslate = false
) => {
const element = document.getElementById("simple-translate"); const element = document.getElementById("simple-translate");
if (element) return; if (element) return;
if (!isEnabled) return;
document.body.insertAdjacentHTML("beforeend", "<div id='simple-translate'></div>"); document.body.insertAdjacentHTML("beforeend", "<div id='simple-translate'></div>");
ReactDOM.render( ReactDOM.render(
<TranslateContainer <TranslateContainer
removeElement={removeElement} removeContainer={removeTranslatecontainer}
insertElement={insertElement} selectedText={selectedText}
isFirst={isFirst} selectedPosition={selectedPosition}
clickedPosition={clickedPosition}
shouldTranslate={shouldTranslate}
/>, />,
document.getElementById("simple-translate") document.getElementById("simple-translate")
); );
isFirst = false;
isEnabled = true;
}; };
insertElement();