Replace content scripts with React
This commit is contained in:
parent
611658c275
commit
c23837e6e1
36
package-lock.json
generated
36
package-lock.json
generated
|
@ -1137,6 +1137,12 @@
|
|||
"uri-js": "^4.2.1"
|
||||
}
|
||||
},
|
||||
"ajv-errors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
|
||||
"integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ajv-keywords": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz",
|
||||
|
@ -5367,6 +5373,12 @@
|
|||
"brorand": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz",
|
||||
"integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==",
|
||||
"dev": true
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.36.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz",
|
||||
|
@ -9705,6 +9717,30 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"url-loader": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz",
|
||||
"integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^1.1.0",
|
||||
"mime": "^2.0.3",
|
||||
"schema-utils": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"schema-utils": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
|
||||
"integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^6.1.0",
|
||||
"ajv-errors": "^1.0.0",
|
||||
"ajv-keywords": "^3.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"use": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"sass-loader": "^7.0.3",
|
||||
"style-loader": "^0.21.0",
|
||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||
"url-loader": "^1.1.2",
|
||||
"webextension-polyfill": "^0.3.1",
|
||||
"webpack": "^4.10.2",
|
||||
"webpack-cli": "^3.0.1",
|
||||
|
|
|
@ -1,86 +1,75 @@
|
|||
/* Copyright (c) 2017-2018 Sienori All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
let translationHistory = [];
|
||||
|
||||
class Translate {
|
||||
constructor() {
|
||||
this.history = [];
|
||||
}
|
||||
const getHistory = (sourceWord, sourceLang, targetLang) => {
|
||||
const history = translationHistory.find(
|
||||
history =>
|
||||
history.sourceWord == sourceWord &&
|
||||
history.sourceLang == sourceLang &&
|
||||
history.targetLang == targetLang &&
|
||||
history.result.statusText == "OK"
|
||||
);
|
||||
return history;
|
||||
};
|
||||
|
||||
getHistory(sourceWord, sourceLang, targetLang) {
|
||||
const history = this.history.find(
|
||||
history =>
|
||||
history.sourceWord == sourceWord &&
|
||||
history.sourceLang == sourceLang &&
|
||||
history.targetLang == targetLang &&
|
||||
history.result.statusText == "OK"
|
||||
);
|
||||
return history;
|
||||
}
|
||||
const setHistory = (sourceWord, sourceLang, targetLang, formattedResult) => {
|
||||
translationHistory.push({
|
||||
sourceWord: sourceWord,
|
||||
sourceLang: sourceLang,
|
||||
targetLang: targetLang,
|
||||
result: formattedResult
|
||||
});
|
||||
};
|
||||
|
||||
setHistory(sourceWord, sourceLang, targetLang, formattedResult) {
|
||||
this.history.push({
|
||||
sourceWord: sourceWord,
|
||||
sourceLang: sourceLang,
|
||||
targetLang: targetLang,
|
||||
result: formattedResult
|
||||
});
|
||||
}
|
||||
const sendRequest = (word, sourceLang, targetLang) => {
|
||||
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sourceLang}&tl=${targetLang}&dt=t&dt=bd&dj=1&q=${encodeURIComponent(
|
||||
word
|
||||
)}`;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.responseType = "json";
|
||||
xhr.open("GET", url);
|
||||
xhr.send();
|
||||
|
||||
async translate(sourceWord, sourceLang = "auto", targetLang) {
|
||||
sourceWord = sourceWord.trim();
|
||||
|
||||
const history = this.getHistory(sourceWord, sourceLang, targetLang);
|
||||
if (history) return history.result;
|
||||
|
||||
const result = await this.sendRequest(sourceWord, sourceLang, targetLang);
|
||||
const formattedResult = this.formatResult(result);
|
||||
this.setHistory(sourceWord, sourceLang, targetLang, formattedResult);
|
||||
|
||||
return formattedResult;
|
||||
}
|
||||
|
||||
sendRequest(word, sourceLang, targetLang) {
|
||||
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sourceLang}&tl=${targetLang}&dt=t&dt=bd&dj=1&q=${encodeURIComponent(
|
||||
word
|
||||
)}`;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.responseType = "json";
|
||||
xhr.open("GET", url);
|
||||
xhr.send();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
xhr.onload = () => {
|
||||
resolve(xhr);
|
||||
};
|
||||
xhr.onerror = () => {
|
||||
resolve(xhr);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
formatResult(result) {
|
||||
const resultData = {
|
||||
resultText: "",
|
||||
candidateText: "",
|
||||
sourceLanguage: "",
|
||||
percentage: 0,
|
||||
statusText: ""
|
||||
return new Promise((resolve, reject) => {
|
||||
xhr.onload = () => {
|
||||
resolve(xhr);
|
||||
};
|
||||
xhr.onerror = () => {
|
||||
resolve(xhr);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
resultData.statusText = result.statusText;
|
||||
if (resultData.statusText !== "OK") return resultData;
|
||||
const formatResult = result => {
|
||||
const resultData = {
|
||||
resultText: "",
|
||||
candidateText: "",
|
||||
sourceLanguage: "",
|
||||
percentage: 0,
|
||||
statusText: ""
|
||||
};
|
||||
|
||||
resultData.sourceLanguage = result.response.src;
|
||||
resultData.percentage = result.response.confidence;
|
||||
resultData.resultText = result.response.sentences.map(sentence => sentence.trans).join("");
|
||||
if (result.response.dict) {
|
||||
resultData.candidateText = result.response.dict
|
||||
.map(dict => `${dict.pos}${dict.pos != "" ? ": " : ""}${dict.terms.join(", ")}\n`)
|
||||
.join("");
|
||||
}
|
||||
resultData.statusText = result.statusText;
|
||||
if (resultData.statusText !== "OK") return resultData;
|
||||
|
||||
return resultData;
|
||||
resultData.sourceLanguage = result.response.src;
|
||||
resultData.percentage = result.response.confidence;
|
||||
resultData.resultText = result.response.sentences.map(sentence => sentence.trans).join("");
|
||||
if (result.response.dict) {
|
||||
resultData.candidateText = result.response.dict
|
||||
.map(dict => `${dict.pos}${dict.pos != "" ? ": " : ""}${dict.terms.join(", ")}\n`)
|
||||
.join("");
|
||||
}
|
||||
}
|
||||
|
||||
return resultData;
|
||||
};
|
||||
|
||||
export default async (sourceWord, sourceLang = "auto", targetLang) => {
|
||||
sourceWord = sourceWord.trim();
|
||||
const history = getHistory(sourceWord, sourceLang, targetLang);
|
||||
if (history) return history.result;
|
||||
|
||||
const result = await sendRequest(sourceWord, sourceLang, targetLang);
|
||||
const formattedResult = formatResult(result);
|
||||
setHistory(sourceWord, sourceLang, targetLang, formattedResult);
|
||||
return formattedResult;
|
||||
};
|
||||
|
|
37
src/content/components/TranslateButton.js
Normal file
37
src/content/components/TranslateButton.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import React from "react";
|
||||
import { getSettings } from "src/settings/settings";
|
||||
import "../styles/TranslateButton.scss";
|
||||
|
||||
const calcPosition = () => {
|
||||
const buttonSize = parseInt(getSettings("buttonSize"));
|
||||
const offset = 10;
|
||||
switch (getSettings("buttonPosition")) {
|
||||
case "rightUp":
|
||||
return { top: -buttonSize - offset, left: offset };
|
||||
case "rightDown":
|
||||
return { top: offset, left: offset };
|
||||
case "leftUp":
|
||||
return { top: -buttonSize - offset, left: -buttonSize - offset };
|
||||
case "leftDown":
|
||||
return { top: offset, left: -buttonSize - offset };
|
||||
}
|
||||
};
|
||||
|
||||
export default props => {
|
||||
const { position, shouldShow } = props;
|
||||
const buttonSize = parseInt(getSettings("buttonSize"));
|
||||
const { top, left } = calcPosition();
|
||||
const buttonStyle = {
|
||||
height: buttonSize,
|
||||
width: buttonSize,
|
||||
top: top + position.y,
|
||||
left: left + position.x
|
||||
};
|
||||
return (
|
||||
<button
|
||||
style={buttonStyle}
|
||||
className={`simple-translate-button ${shouldShow ? "isShow" : ""}`}
|
||||
onClick={props.handleButtonClick}
|
||||
/>
|
||||
);
|
||||
};
|
181
src/content/components/TranslateContainer.js
Normal file
181
src/content/components/TranslateContainer.js
Normal file
|
@ -0,0 +1,181 @@
|
|||
import React, { Component } from "react";
|
||||
import browser from "webextension-polyfill";
|
||||
import translate from "src/common/translate";
|
||||
import { initSettings, getSettings, handleSettingsChange } from "src/settings/settings";
|
||||
import TranslateButton from "./TranslateButton";
|
||||
import TranslatePanel from "./TranslatePanel";
|
||||
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();
|
||||
const selectedPosition = {
|
||||
x: selectedRect.left + selectedRect.width / 2,
|
||||
y: selectedRect.bottom
|
||||
};
|
||||
return selectedPosition;
|
||||
};
|
||||
|
||||
const translateText = async text => {
|
||||
const targetLang = getSettings("targetLang");
|
||||
const result = await translate(text, "auto", targetLang);
|
||||
return result;
|
||||
};
|
||||
|
||||
const matchesTargetLang = async selectedText => {
|
||||
const targetLang = getSettings("targetLang");
|
||||
//detectLanguageで判定
|
||||
const langInfo = await browser.i18n.detectLanguage(selectedText);
|
||||
const matchsLangsByDetect = langInfo.isReliable && langInfo.languages[0].language === targetLang;
|
||||
if (matchsLangsByDetect) return true;
|
||||
|
||||
//先頭100字を翻訳にかけて判定
|
||||
const partSelectedText = selectedText.substring(0, 100);
|
||||
const result = await translateText(partSelectedText);
|
||||
const matchsLangs = targetLang === result.sourceLanguage && result.percentage > 0;
|
||||
return matchsLangs;
|
||||
};
|
||||
|
||||
export default class TranslateContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isInit: false,
|
||||
shouldShowButton: false,
|
||||
buttonPosition: { x: 0, y: 0 },
|
||||
shouldShowPanel: false,
|
||||
panelPosition: { x: 0, y: 0 },
|
||||
resultText: "",
|
||||
candidateText: ""
|
||||
};
|
||||
this.selectedText = "";
|
||||
this.init();
|
||||
}
|
||||
|
||||
init = async () => {
|
||||
await initSettings();
|
||||
this.setState({ isInit: true });
|
||||
document.addEventListener("mouseup", e => setTimeout(() => this.handleMouseUp(e), 0));
|
||||
document.addEventListener("keydown", this.handleKeyDown);
|
||||
browser.storage.onChanged.addListener(handleSettingsChange);
|
||||
browser.runtime.onMessage.addListener(this.handleMessage);
|
||||
};
|
||||
|
||||
handleMessage = async request => {
|
||||
switch (request.message) {
|
||||
case "getTabInfo":
|
||||
const tabInfo = { url: location.href, selectedText: this.selectedText };
|
||||
return tabInfo;
|
||||
case "translateSelectedText":
|
||||
this.selectedText = getSelectedText();
|
||||
const position = getSelectedPosition();
|
||||
if (this.selectedText.length === 0) return;
|
||||
this.hideButton();
|
||||
this.showPanel(position);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
handleKeyDown = e => {
|
||||
if (e.key === "Escape") {
|
||||
this.hideButton();
|
||||
this.hidePanel();
|
||||
}
|
||||
};
|
||||
|
||||
showButton = position => {
|
||||
this.setState({ shouldShowButton: true, buttonPosition: position });
|
||||
};
|
||||
|
||||
hideButton = () => {
|
||||
this.setState({ shouldShowButton: false });
|
||||
};
|
||||
|
||||
handleButtonClick = e => {
|
||||
const position = { x: e.clientX, y: e.clientY };
|
||||
this.showPanel(position);
|
||||
this.hideButton();
|
||||
};
|
||||
|
||||
showPanel = async position => {
|
||||
const result = await translateText(this.selectedText);
|
||||
this.setState({
|
||||
shouldShowPanel: true,
|
||||
panelPosition: position,
|
||||
resultText: result.resultText,
|
||||
candidateText: getSettings("ifShowCandidate") ? result.candidateText : ""
|
||||
});
|
||||
};
|
||||
|
||||
hidePanel = () => {
|
||||
this.setState({ shouldShowPanel: false });
|
||||
};
|
||||
|
||||
handleMouseUp = e => {
|
||||
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();
|
||||
const position = { x: e.clientX, y: e.clientY };
|
||||
|
||||
if (this.selectedText.length === 0) return;
|
||||
this.handleTextSelect(position);
|
||||
};
|
||||
|
||||
handleTextSelect = async position => {
|
||||
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(position);
|
||||
} else if (onSelectBehavior === "showPanel") {
|
||||
this.showPanel(position);
|
||||
}
|
||||
};
|
||||
|
||||
render = () => {
|
||||
if (!this.state.isInit) return null;
|
||||
return (
|
||||
<div>
|
||||
<TranslateButton
|
||||
shouldShow={this.state.shouldShowButton}
|
||||
position={this.state.buttonPosition}
|
||||
handleButtonClick={this.handleButtonClick}
|
||||
/>
|
||||
<TranslatePanel
|
||||
shouldShow={this.state.shouldShowPanel}
|
||||
position={this.state.panelPosition}
|
||||
resultText={this.state.resultText}
|
||||
candidateText={this.state.candidateText}
|
||||
hidePanel={this.hidePanel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
116
src/content/components/TranslatePanel.js
Normal file
116
src/content/components/TranslatePanel.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
import React, { Component } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { getSettings } from "src/settings/settings";
|
||||
import "../styles/TranslatePanel.scss";
|
||||
|
||||
export default class TranslatePanel extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
panelPosition: { x: 0, y: 0 },
|
||||
panelWidth: 0,
|
||||
panelHeight: 0,
|
||||
shouldResize: true
|
||||
};
|
||||
this.isFirst = true;
|
||||
}
|
||||
|
||||
calcPosition = () => {
|
||||
const maxWidth = parseInt(getSettings("width"));
|
||||
const maxHeight = parseInt(getSettings("height"));
|
||||
const wrapper = ReactDOM.findDOMNode(this.refs.wrapper);
|
||||
const panelWidth = Math.min(wrapper.clientWidth, maxWidth);
|
||||
const panelHeight = Math.min(wrapper.clientHeight, maxHeight);
|
||||
const windowWidth = document.documentElement.clientWidth;
|
||||
const windowHeight = document.documentElement.clientHeight;
|
||||
const offset = 10;
|
||||
|
||||
//TODO: パネルの表示位置オプション
|
||||
let position = {
|
||||
x: this.props.position.x - panelWidth / 2,
|
||||
y: this.props.position.y + offset
|
||||
};
|
||||
|
||||
if (position.x + panelWidth > windowWidth - offset) {
|
||||
position.x = windowWidth - panelWidth - offset;
|
||||
}
|
||||
if (position.y + panelHeight > windowHeight - offset) {
|
||||
position.y = windowHeight - panelHeight - offset;
|
||||
}
|
||||
if (position.x < 0 + offset) {
|
||||
position.x = offset;
|
||||
}
|
||||
if (position.y < 0 + offset) {
|
||||
position.y = offset;
|
||||
}
|
||||
return position;
|
||||
};
|
||||
|
||||
calcSize = () => {
|
||||
const wrapper = ReactDOM.findDOMNode(this.refs.wrapper);
|
||||
const wrapperWidth = wrapper.clientWidth;
|
||||
const wrapperHeight = wrapper.clientHeight;
|
||||
return { panelWidth: wrapperWidth, panelHeight: wrapperHeight };
|
||||
};
|
||||
|
||||
componentWillReceiveProps = nextProps => {
|
||||
const isChangedContents =
|
||||
this.props.resultText !== nextProps.resultText ||
|
||||
this.props.candidateText !== nextProps.candidateText;
|
||||
|
||||
if (isChangedContents && nextProps.shouldShow) this.setState({ shouldResize: true });
|
||||
};
|
||||
|
||||
componentDidUpdate = () => {
|
||||
if (!this.state.shouldResize || !this.props.shouldShow) return;
|
||||
const panelPosition = this.calcPosition();
|
||||
const { panelWidth, panelHeight } = this.calcSize();
|
||||
this.setState({
|
||||
shouldResize: false,
|
||||
panelPosition: panelPosition,
|
||||
panelWidth: panelWidth,
|
||||
panelHeight: panelHeight
|
||||
});
|
||||
};
|
||||
|
||||
render = () => {
|
||||
const { width, height } = this.state.shouldResize
|
||||
? { width: parseInt(getSettings("width")), height: parseInt(getSettings("height")) }
|
||||
: { width: this.state.panelWidth, height: this.state.panelHeight };
|
||||
|
||||
const panelStyles = {
|
||||
width: width,
|
||||
height: height,
|
||||
top: this.state.panelPosition.y,
|
||||
left: this.state.panelPosition.x,
|
||||
fontSize: parseInt(getSettings("fontSize")),
|
||||
backgroundColor: getSettings("bgColor")
|
||||
};
|
||||
const wrapperStyles = {
|
||||
overflow: this.state.shouldResize ? "hidden" : "auto"
|
||||
};
|
||||
const resultStyles = {
|
||||
color: getSettings("resultFontColor")
|
||||
};
|
||||
const candidateStyles = {
|
||||
color: getSettings("candidateFontColor")
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`simple-translate-panel ${this.props.shouldShow ? "isShow" : ""}`}
|
||||
ref="panel"
|
||||
style={panelStyles}
|
||||
>
|
||||
<div className="simple-translate-result-wrapper" ref="wrapper" style={wrapperStyles}>
|
||||
<p className="simple-translate-result" style={resultStyles}>
|
||||
{this.props.resultText}
|
||||
</p>
|
||||
<p className="simple-translate-candidate" style={candidateStyles}>
|
||||
{this.props.candidateText}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
6
src/content/index.js
Normal file
6
src/content/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import TranslateContainer from "./components/TranslateContainer";
|
||||
|
||||
document.body.insertAdjacentHTML("afterend", "<div id='simple-translate'></div>");
|
||||
ReactDOM.render(<TranslateContainer />, document.getElementById("simple-translate"));
|
40
src/content/styles/TranslateButton.scss
Normal file
40
src/content/styles/TranslateButton.scss
Normal file
|
@ -0,0 +1,40 @@
|
|||
#simple-translate {
|
||||
.simple-translate-button {
|
||||
all: initial;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08);
|
||||
border-radius: 10%;
|
||||
background-image: url("../../icons/512.png");
|
||||
background-size: 75%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
position: fixed;
|
||||
z-index: 2147483647;
|
||||
cursor: pointer;
|
||||
|
||||
transition-property: opacity, visibility;
|
||||
transition-duration: 200ms;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
&.isShow {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition-duration: 0ms;
|
||||
animation: simple-translate-showButton 200ms;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes simple-translate-showButton {
|
||||
0% {
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
50% {
|
||||
transform: scale3d(1.1, 1.1, 1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
8
src/content/styles/TranslateContainer.scss
Normal file
8
src/content/styles/TranslateContainer.scss
Normal file
|
@ -0,0 +1,8 @@
|
|||
:root {
|
||||
--simple-translate-main-text: #0c0c0d;
|
||||
--simple-translate-sub-text: #737373;
|
||||
--simple-translate-line: #ededf0;
|
||||
--simple-translate-button: #d7d7db;
|
||||
--simple-translate-highlight: #5595ff;
|
||||
--simple-translate-main-bg: #ffffff;
|
||||
}
|
56
src/content/styles/TranslatePanel.scss
Normal file
56
src/content/styles/TranslatePanel.scss
Normal file
|
@ -0,0 +1,56 @@
|
|||
#simple-translate {
|
||||
.simple-translate-panel {
|
||||
all: initial;
|
||||
position: fixed;
|
||||
background-color: var(--simple-translate-main-bg);
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08);
|
||||
border-radius: 3px;
|
||||
z-index: 2147483646;
|
||||
|
||||
transition-property: opacity, visibility;
|
||||
transition-duration: 200ms;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
&.isShow {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.simple-translate-result-wrapper {
|
||||
padding: 10px 18px;
|
||||
box-sizing: border-box;
|
||||
width: max-content;
|
||||
height: max-content;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
p {
|
||||
all: initial;
|
||||
display: block;
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", "San Francisco", "Ubuntu", "Fira Sans", "Roboto", "Arial",
|
||||
"Helvetica", sans-serif !important;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
font-size: inherit;
|
||||
line-height: 150%;
|
||||
visibility: inherit;
|
||||
opacity: inherit;
|
||||
|
||||
&::-moz-selection {
|
||||
background: var(--simple-translate-line);
|
||||
}
|
||||
|
||||
&.simple-translate-result {
|
||||
color: var(--simple-translate-main-text);
|
||||
}
|
||||
&.simple-translate-candidate {
|
||||
color: var(--simple-translate-sub-text);
|
||||
margin-top: 1em;
|
||||
&:empty {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,8 +43,7 @@
|
|||
{
|
||||
"all_frames": true,
|
||||
"matches": ["http://*/*", "https://*/*", "<all_urls>"],
|
||||
"css": ["content/simple-translate.css"],
|
||||
"js": ["content/simple-translate.js"]
|
||||
"js": ["content/content.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -49,8 +49,7 @@
|
|||
{
|
||||
"all_frames": true,
|
||||
"matches": ["http://*/*", "https://*/*", "<all_urls>"],
|
||||
"css": ["content/simple-translate.css"],
|
||||
"js": ["content/simple-translate.js"]
|
||||
"js": ["content/content.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* Copyright (c) 2018 Kamil Mikosz
|
||||
* Copyright (c) 2019 Sienori
|
||||
* Released under the MIT license.
|
||||
* see https://opensource.org/licenses/MIT */
|
||||
|
||||
|
@ -72,6 +73,11 @@ const generalConfig = {
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif)$/,
|
||||
loader: "url-loader",
|
||||
options: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -79,6 +79,11 @@ const generalConfig = {
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif)$/,
|
||||
loader: "url-loader",
|
||||
options: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ const getEntry = (sourceDir = "src") => {
|
|||
return {
|
||||
popup: path.resolve(__dirname, `${sourceDir}/popup/index.js`),
|
||||
options: path.resolve(__dirname, `${sourceDir}/options/index.js`),
|
||||
content: path.resolve(__dirname, `${sourceDir}/content/simple-translate.js`),
|
||||
content: path.resolve(__dirname, `${sourceDir}/content/index.js`),
|
||||
background: path.resolve(__dirname, `${sourceDir}/background/background.js`)
|
||||
};
|
||||
};
|
||||
|
@ -49,10 +49,6 @@ const getCopyPlugins = (browserDir, outputDir = "dev", sourceDir = "src") => [
|
|||
from: `${sourceDir}/_locales`,
|
||||
to: path.resolve(__dirname, `${outputDir}/${browserDir}/_locales`)
|
||||
},
|
||||
{
|
||||
from: `${sourceDir}/content/simple-translate.css`,
|
||||
to: path.resolve(__dirname, `${outputDir}/${browserDir}/content/simple-translate.css`)
|
||||
},
|
||||
{
|
||||
from: `${sourceDir}/manifest-chrome.json`,
|
||||
to: path.resolve(__dirname, `${outputDir}/${browserDir}/manifest.json`)
|
||||
|
@ -70,10 +66,6 @@ const getFirefoxCopyPlugins = (browserDir, outputDir = "dev", sourceDir = "src")
|
|||
from: `${sourceDir}/_locales`,
|
||||
to: path.resolve(__dirname, `${outputDir}/${browserDir}/_locales`)
|
||||
},
|
||||
{
|
||||
from: `${sourceDir}/content/simple-translate.css`,
|
||||
to: path.resolve(__dirname, `${outputDir}/${browserDir}/content/simple-translate.css`)
|
||||
},
|
||||
{
|
||||
from: `${sourceDir}/manifest-firefox.json`,
|
||||
to: path.resolve(__dirname, `${outputDir}/${browserDir}/manifest.json`)
|
||||
|
|
Loading…
Reference in a new issue