diff --git a/src/common/openUrl.js b/src/common/openUrl.js
new file mode 100644
index 0000000..04623ef
--- /dev/null
+++ b/src/common/openUrl.js
@@ -0,0 +1,6 @@
+import browser from "webextension-polyfill";
+
+export default async url => {
+ const activeTab = (await browser.tabs.query({ currentWindow: true, active: true }))[0];
+ browser.tabs.create({ url: url, index: activeTab.index + 1 });
+};
diff --git a/src/common/translate.js b/src/common/translate.js
index 6025ee0..4177e4d 100644
--- a/src/common/translate.js
+++ b/src/common/translate.js
@@ -65,6 +65,15 @@ const formatResult = result => {
export default async (sourceWord, sourceLang = "auto", targetLang) => {
sourceWord = sourceWord.trim();
+ if (sourceWord === "")
+ return {
+ resultText: "",
+ candidateText: "",
+ sourceLanguage: "en",
+ percentage: 0,
+ statusText: "OK"
+ };
+
const history = getHistory(sourceWord, sourceLang, targetLang);
if (history) return history.result;
diff --git a/src/popup/components/Footer.js b/src/popup/components/Footer.js
new file mode 100644
index 0000000..0b67a10
--- /dev/null
+++ b/src/popup/components/Footer.js
@@ -0,0 +1,50 @@
+import React, { Component } from "react";
+import browser from "webextension-polyfill";
+import genelateLangOptions from "src/common/genelateLangOptions";
+import openUrl from "src/common/openUrl";
+import "../styles/Footer.scss";
+
+export default class Footer extends Component {
+ constructor(props) {
+ super(props);
+ this.langList = genelateLangOptions();
+ }
+
+ handleLinkClick = async () => {
+ const { tabUrl, targetLang } = this.props;
+ const encodedUrl = encodeURIComponent(tabUrl);
+ const translateUrl = `https://translate.google.com/translate?hl=${targetLang}&sl=auto&u=${encodedUrl}`;
+ openUrl(translateUrl);
+ };
+
+ handleChange = e => {
+ const lang = e.target.value;
+ this.props.handleLangChange(lang);
+ };
+
+ render() {
+ const { tabUrl, targetLang } = this.props;
+
+ return (
+
+ );
+ }
+}
diff --git a/src/popup/components/Header.js b/src/popup/components/Header.js
new file mode 100644
index 0000000..53ca3cd
--- /dev/null
+++ b/src/popup/components/Header.js
@@ -0,0 +1,42 @@
+import React from "react";
+import browser from "webextension-polyfill";
+import browserInfo from "browser-info";
+import openUrl from "src/common/openUrl";
+import HeartIcon from "../icons/heart.svg";
+import SettingsIcon from "../icons/settings.svg";
+import "../styles/header.scss";
+
+//TODO: 次のタブで開く
+const openPayPal = () => {
+ const isChrome = browserInfo().name === "Chrome";
+ const url = `https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&no_shipping=1&business=sienori.firefox@gmail.com&item_name=SimpleTranslate ${
+ isChrome ? "for Chrome " : ""
+ }- Donation`;
+ openUrl(url);
+};
+const openSettings = () => {
+ const url = "../options/index.html#settings";
+ openUrl(url);
+};
+
+export default () => (
+
+);
diff --git a/src/popup/components/InputArea.js b/src/popup/components/InputArea.js
new file mode 100644
index 0000000..426d654
--- /dev/null
+++ b/src/popup/components/InputArea.js
@@ -0,0 +1,33 @@
+import React, { Component } from "react";
+import ReactDOM from "react-dom";
+import browser from "webextension-polyfill";
+import "../styles/inputArea.scss";
+
+export default class InputArea extends Component {
+ resizeTextArea = () => {
+ const textarea = ReactDOM.findDOMNode(this.refs.textarea);
+ textarea.style.height = "1px";
+ textarea.style.height = `${textarea.scrollHeight + 2}px`;
+ };
+
+ handleInputText = e => {
+ const inputText = e.target.value;
+ this.props.handleInputText(inputText);
+ this.resizeTextArea();
+ };
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
diff --git a/src/popup/components/PopupPage.js b/src/popup/components/PopupPage.js
new file mode 100644
index 0000000..9eda022
--- /dev/null
+++ b/src/popup/components/PopupPage.js
@@ -0,0 +1,123 @@
+import React, { Component } from "react";
+import browser from "webextension-polyfill";
+import { initSettings, getSettings } from "src/settings/settings";
+import translate from "src/common/translate";
+import Header from "./Header";
+import InputArea from "./InputArea";
+import ResultArea from "./ResultArea";
+import Footer from "./Footer";
+import "../styles/PopupPage.scss";
+
+const getTabInfo = async () => {
+ try {
+ const tab = (await browser.tabs.query({ currentWindow: true, active: true }))[0];
+ const tabInfo = await browser.tabs.sendMessage(tab.id, { message: "getTabInfo" });
+ return tabInfo;
+ } catch (e) {
+ return { url: "", selectedText: "" };
+ }
+};
+
+export default class PopupPage extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ targetLang: "",
+ inputText: "",
+ resultText: "",
+ candidateText: "",
+ statusText: "OK",
+ tabUrl: ""
+ };
+ this.isSwitchedSecondLang = false;
+ this.init();
+ }
+
+ init = async () => {
+ await initSettings();
+
+ const targetLang = getSettings("targetLang");
+ this.setState({
+ targetLang: targetLang
+ });
+
+ const tabInfo = await getTabInfo();
+ this.setState({
+ inputText: tabInfo.selectedText,
+ tabUrl: tabInfo.url
+ });
+ if (tabInfo.selectedText !== "") this.translateText(tabInfo.selectedText, targetLang);
+ };
+
+ handleInputText = inputText => {
+ this.setState({ inputText: inputText });
+
+ const waitTime = getSettings("waitTime");
+ clearTimeout(this.inputTimer);
+ this.inputTimer = setTimeout(
+ () => this.translateText(inputText, this.state.targetLang),
+ waitTime
+ );
+ };
+
+ handleLangChange = lang => {
+ this.setState({ targetLang: lang });
+ const inputText = this.state.inputText;
+ if (inputText !== "") this.translateText(inputText, lang);
+ };
+
+ translateText = async (text, targetLang) => {
+ const result = await translate(text, "auto", targetLang);
+ this.setState({
+ resultText: result.resultText,
+ candidateText: result.candidateText,
+ statusText: result.statusText
+ });
+ this.switchSecondLang(result);
+ };
+
+ switchSecondLang = result => {
+ if (!getSettings("ifChangeSecondLang")) return;
+
+ const defaultTargetLang = getSettings("targetLang");
+ const secondLang = getSettings("secondTargetLang");
+ if (defaultTargetLang === secondLang) return;
+
+ const equalsSourceAndTarget =
+ result.sourceLanguage === this.state.targetLang && result.percentage > 0;
+ const equalsSourceAndDefault =
+ result.sourceLanguage === defaultTargetLang && result.percentage > 0;
+
+ if (!this.isSwitchedSecondLang) {
+ if (equalsSourceAndTarget && equalsSourceAndDefault) {
+ this.handleLangChange(secondLang);
+ this.isSwitchedSecondLang = true;
+ }
+ } else {
+ if (!equalsSourceAndDefault) {
+ this.handleLangChange(defaultTargetLang);
+ this.isSwitchedSecondLang = false;
+ }
+ }
+ };
+
+ render() {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/popup/components/ResultArea.js b/src/popup/components/ResultArea.js
new file mode 100644
index 0000000..6e21fe0
--- /dev/null
+++ b/src/popup/components/ResultArea.js
@@ -0,0 +1,38 @@
+import React from "react";
+import browser from "webextension-polyfill";
+import "../styles/ResultArea.scss";
+
+const getErrorMessage = statusText => {
+ let errorMessage = "";
+ switch (statusText) {
+ case "":
+ errorMessage = browser.i18n.getMessage("networkError");
+ break;
+ case "Service Unavailable":
+ errorMessage = browser.i18n.getMessage("unavailableError");
+ break;
+ default:
+ errorMessage = `${browser.i18n.getMessage("unknownError")} [${statusText}]`;
+ break;
+ }
+ return errorMessage;
+};
+
+const splitLine = text => {
+ const regex = /(\n)/g;
+ return text.split(regex).map((line, i) => (line.match(regex) ?
: line));
+};
+
+export default props => {
+ const { resultText, candidateText, statusText } = props;
+ const isError = statusText !== "OK";
+
+ return (
+
+
{splitLine(resultText)}
+
+ {isError ? getErrorMessage(statusText) : splitLine(candidateText)}
+
+
+ );
+};
diff --git a/src/popup/icons/heart.svg b/src/popup/icons/heart.svg
new file mode 100644
index 0000000..4703056
--- /dev/null
+++ b/src/popup/icons/heart.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/src/popup/icons/settings.svg b/src/popup/icons/settings.svg
new file mode 100644
index 0000000..e6f05b8
--- /dev/null
+++ b/src/popup/icons/settings.svg
@@ -0,0 +1,20 @@
+
\ No newline at end of file
diff --git a/src/popup/index.html b/src/popup/index.html
index e5a0a6a..2558c6c 100644
--- a/src/popup/index.html
+++ b/src/popup/index.html
@@ -1,81 +1,12 @@
-
-
+
-
-
+
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/popup/index.js b/src/popup/index.js
index 3708e59..fe37619 100644
--- a/src/popup/index.js
+++ b/src/popup/index.js
@@ -1,231 +1,5 @@
-/* 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/. */
+import React from "react";
+import ReactDOM from "react-dom";
+import PopupPage from "./components/PopupPage";
-const S = new settingsObj();
-const T = new Translate();
-
-//設定を読み出し
-S.init().then(function(value) {
- defaultTargetLang = value.targetLang;
- secondTargetLang = value.secondTargetLang;
- langList.value = value.targetLang; //リスト初期値をセット
- langList.addEventListener("change", changeLang);
-
- document.body.style.fontSize = value.fontSize;
-});
-
-let target = document.getElementById("target");
-let langList = document.getElementById("langList");
-let textarea = document.getElementById("textarea");
-
-const initialText = browser.i18n.getMessage("initialTextArea");
-textarea.placeholder = initialText;
-
-let secondTargetLang;
-let defaultTargetLang;
-let sourceWord = "";
-
-setLangList();
-
-function setLangList() {
- let langListStr = browser.i18n.getMessage("langList");
- langListStr = langListStr.split(", ");
-
- for (let i in langListStr) {
- langListStr[i] = langListStr[i].split(":");
- }
- langListStr = langListStr.sort(alphabeticallySort);
-
- let langListHtml = "";
- for (let i of langListStr) {
- langListHtml += ``;
- }
-
- langList.innerHTML = langListHtml;
-}
-
-setTitles();
-function setTitles() {
- document.getElementById("donate").title = browser.i18n.getMessage("donateWithPaypalLabel");
- document.getElementById("setting").title = browser.i18n.getMessage("settingsLabel");
- document.getElementById("langList").title = browser.i18n.getMessage("targetLangLabel");
-}
-
-function alphabeticallySort(a, b) {
- if (a[1].toString() > b[1].toString()) {
- return 1;
- } else {
- return -1;
- }
-}
-
-//翻訳先言語変更時に更新
-async function changeLang() {
- if (typeof url != "undefined") showLink();
-
- if (sourceWord !== "") {
- const resultData = await T.translate(sourceWord, undefined, langList.value);
- showResult(resultData.resultText, resultData.candidateText, resultData.statusText);
- }
-}
-
-//アクティブなタブを取得して渡す
-browser.tabs
- .query({
- currentWindow: true,
- active: true
- })
- .then(function(tabs) {
- getSelectionWord(tabs);
- });
-
-//アクティブタブから選択文字列とurlを取得
-function getSelectionWord(tabs) {
- for (let tab of tabs) {
- browser.tabs
- .sendMessage(tab.id, {
- message: "fromPopup"
- })
- .then(response => {
- sourceWord = response.word || "";
- url = response.url;
- refleshSource();
- showLink();
- })
- .catch(() => {});
- }
-}
-
-//ページ翻訳へのリンクを表示
-function showLink() {
- document.getElementById("link").innerHTML =
- "" +
- browser.i18n.getMessage("showLink") +
- "";
-}
-
-//翻訳元テキストを表示
-function refleshSource() {
- if (sourceWord !== "") {
- textarea.innerHTML = sourceWord;
- resize();
- inputText();
- }
-}
-
-textarea.addEventListener("paste", () => {
- resize();
- inputText();
-});
-
-textarea.addEventListener("keydown", resize);
-
-textarea.addEventListener("keyup", function(event) {
- if (sourceWord == textarea.value) return;
-
- resize();
- inputText();
-});
-
-//テキストボックスをリサイズ
-function resize() {
- setTimeout(function() {
- textarea.style.height = "0px";
- textarea.style.height = parseInt(textarea.scrollHeight) + "px";
- }, 0);
-}
-
-textarea.addEventListener("click", textAreaClick, {
- once: true
-});
-//テキストエリアクリック時の処理
-function textAreaClick() {
- textarea.select();
-}
-
-let inputTimer;
-//文字入力時の処理
-function inputText() {
- sourceWord = textarea.value;
- const waitTime = S.get().waitTime;
-
- clearTimeout(inputTimer);
- inputTimer = setTimeout(() => {
- runTranslation();
- }, waitTime);
-}
-
-async function runTranslation() {
- if (sourceWord == "") {
- showResult("", "");
- return;
- }
-
- const resultData = await T.translate(sourceWord, "auto", langList.value);
- changeSecondLang(defaultTargetLang, resultData.sourceLanguage, resultData.percentage);
- showResult(resultData.resultText, resultData.candidateText, resultData.statusText);
-}
-
-function showResult(resultText, candidateText, statusText = "OK") {
- const resultArea = target.getElementsByClassName("result")[0];
- const candidateArea = target.getElementsByClassName("candidate")[0];
-
- resultArea.innerText = resultText;
- if (S.get().ifShowCandidate) candidateArea.innerText = candidateText;
-
- if (statusText != "OK") showError(statusText);
-}
-
-function showError(statusText) {
- let errorMessage = "";
- switch (statusText) {
- case "":
- errorMessage = browser.i18n.getMessage("networkError");
- break;
- case "Service Unavailable":
- errorMessage = browser.i18n.getMessage("unavailableError");
- break;
- default:
- errorMessage = `${browser.i18n.getMessage("unknownError")} [${statusText}]`;
- break;
- }
- const candidateArea = target.getElementsByClassName("candidate")[0];
- candidateArea.innerText = errorMessage;
-}
-
-let changeLangFlag = false;
-
-function changeSecondLang(defaultTargetLang, sourceLang, percentage) {
- if (!S.get().ifChangeSecondLang) return;
- //検出された翻訳元言語がターゲット言語と一致
- const equalsSourceAndTarget = sourceLang == langList.value && percentage > 0;
-
- //検出された翻訳元言語がデフォルト言語と一致
- const equalsSourceAndDefault = sourceLang == defaultTargetLang && percentage > 0;
-
- if (!changeLangFlag) {
- //通常時
- if (equalsSourceAndTarget && equalsSourceAndDefault) {
- //ソースとターゲットとデフォルトが一致する場合
- //ターゲットを第2言語に変更
- changeLangFlag = true;
- langList.value = secondTargetLang;
- changeLang();
- }
- } else {
- //第2言語に切替した後
- if (!equalsSourceAndDefault) {
- //ソースとデフォルトが異なる場合
- //ターゲットをデフォルトに戻す
- changeLangFlag = false;
- langList.value = defaultTargetLang;
- changeLang();
- }
- }
-}
+ReactDOM.render(, document.getElementById("root"));
diff --git a/src/popup/popup.css b/src/popup/popup.css
deleted file mode 100644
index b9b5119..0000000
--- a/src/popup/popup.css
+++ /dev/null
@@ -1,227 +0,0 @@
-:root {
- --main-text: #0c0c0d;
- --sub-text: #737373;
- --line: #ededf0;
- --button: #d7d7db;
- --highlight: #5595ff;
- --main-bg: #ffffff;
- --confirm: #ff4f4f;
-}
-
-body {
- font-family: "Segoe UI", "San Francisco", "Ubuntu", "Fira Sans", "Roboto", "Arial", "Helvetica",
- sans-serif;
- text-align: left;
- font-size: 13px;
- width: 348px;
- overflow: hidden;
- background-color: var(--main-bg);
-
- padding: 0px;
- margin: 0px;
-
- display: flex;
- flex-direction: column;
-}
-
-.hidden {
- display: none;
-}
-
-svg {
- pointer-events: none;
-}
-
-#header {
- padding: 10px;
- background-color: var(--line);
-
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
-
- -moz-user-select: none;
-}
-
-#title {
- font-size: 15px;
- font-weight: 400;
- color: #666;
- cursor: default;
-}
-
-#header .rightButtons {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: flex-end;
-}
-
-#donate {
- display: flex;
- align-items: center;
- cursor: pointer;
- margin-right: 8px;
-}
-
-#donate svg {
- height: 18px;
- width: 18px;
- fill: var(--sub-text);
- transition: all 100ms;
-}
-
-#donate:hover svg {
- fill: var(--confirm);
-}
-
-#setting {
- display: flex;
- align-items: center;
- cursor: pointer;
-}
-
-#setting svg {
- flex-shrink: 0;
- height: 18px;
- width: 18px;
- fill: var(--sub-text);
- transform: rotate(180deg);
- transition: fill 100ms, transform 300ms ease;
-}
-
-#setting:hover svg {
- fill: var(--highlight);
- transform: rotate(270deg);
-}
-
-#main {
- padding: 10px;
-}
-
-textarea {
- font: inherit;
- resize: none;
- overflow: auto;
- background-color: var(--main-bg);
-
- max-height: 215px;
- height: 37px;
-
- /* 100% - padding*2 - border*2 */
- width: calc(100% - 22px);
-
- padding: 10px;
- border: solid 1px var(--button);
- border-radius: 2px;
- transition: border-color 100ms ease-out;
-}
-
-textarea:hover,
-textarea:focus {
- border-color: var(--highlight);
-}
-
-hr {
- border: none;
- border-top: solid 1px var(--button);
- height: 1px;
- margin: 10px 0px;
-}
-
-#target {
- max-height: 215px;
- min-height: 30px;
- overflow-y: auto;
- word-wrap: break-word;
- padding: 0px 5px 0px;
- background-color: var(--main-bg);
-}
-
-#target p {
- margin: 0;
- background-color: var(--main-bg);
-}
-
-#target .result {
- background-color: var(--main-bg);
-}
-
-#target .candidate {
- color: var(--sub-text);
- margin-top: 1em;
- background-color: var(--main-bg);
-}
-
-#target .candidate:empty {
- margin-top: 0;
-}
-
-#footer {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- padding: 0px 10px 10px;
-}
-
-#link {
- flex-shrink: 0;
-}
-
-#link a {
- font-style: normal;
- text-decoration: none;
- color: var(--highlight);
-}
-
-#link a:hover {
- text-decoration: underline;
-}
-
-select {
- -moz-appearance: none;
- text-overflow: ellipsis;
- border: var(--button) solid 1px;
- border-radius: 2px;
- padding: 3px 5px;
- padding-right: 20px;
- width: 100%;
- transition: border-color 100ms ease-out;
-}
-
-select:hover {
- border: var(--highlight) solid 1px;
-}
-
-.selectWrap {
- position: relative;
- margin-left: 5px;
-}
-
-.selectWrap:before {
- pointer-events: none;
- content: "";
- z-index: 1;
- position: absolute;
- top: 40%;
- right: 7px;
- width: 5px;
- height: 5px;
-
- transform: rotate(45deg);
- border-bottom: 2px solid var(--sub-text);
- border-right: 2px solid var(--sub-text);
-
- transition: border-color 100ms ease-out;
-}
-
-.selectWrap:hover::before {
- border-bottom: 2px solid var(--highlight);
- border-right: 2px solid var(--highlight);
-}
-
-::-moz-selection {
- background: var(--line);
-}
diff --git a/src/popup/styles/Footer.scss b/src/popup/styles/Footer.scss
new file mode 100644
index 0000000..52df177
--- /dev/null
+++ b/src/popup/styles/Footer.scss
@@ -0,0 +1,57 @@
+#footer {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0px 10px 10px;
+
+ .translateLink {
+ flex-shrink: 0;
+ a {
+ font-style: normal;
+ text-decoration: none;
+ color: var(--highlight);
+ cursor: pointer;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .selectWrap {
+ position: relative;
+ margin-left: 5px;
+ &:before {
+ pointer-events: none;
+ content: "";
+ z-index: 1;
+ position: absolute;
+ top: 40%;
+ right: 7px;
+ width: 5px;
+ height: 5px;
+
+ transform: rotate(45deg);
+ border-bottom: 2px solid var(--sub-text);
+ border-right: 2px solid var(--sub-text);
+
+ transition: border-color 100ms ease-out;
+ }
+ &:hover::before {
+ border-bottom: 2px solid var(--highlight);
+ border-right: 2px solid var(--highlight);
+ }
+
+ select {
+ -moz-appearance: none;
+ text-overflow: ellipsis;
+ background-color: var(--main-bg);
+ border: var(--button) solid 1px;
+ border-radius: 2px;
+ padding: 3px 5px;
+ padding-right: 20px;
+ width: 100%;
+ transition: border-color 100ms ease-out;
+ }
+ }
+}
diff --git a/src/popup/styles/Header.scss b/src/popup/styles/Header.scss
new file mode 100644
index 0000000..d5f133a
--- /dev/null
+++ b/src/popup/styles/Header.scss
@@ -0,0 +1,61 @@
+#header {
+ padding: 10px;
+ background-color: var(--line);
+ display: flex;
+ flex-direction: row;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: space-between;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+
+ .title {
+ font-size: 15px;
+ font-weight: 400;
+ color: #666;
+ cursor: default;
+ flex-shrink: 0;
+ }
+
+ .rightButtons {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-end;
+ height: 18px;
+
+ button {
+ display: block;
+ background-color: transparent;
+ border: none;
+ cursor: pointer;
+ outline: none;
+ padding: 0;
+ }
+
+ .heartButton {
+ display: flex;
+ align-items: center;
+ margin-right: 10px;
+ &:hover svg {
+ fill: var(--confirm);
+ }
+ }
+
+ .settingsButton {
+ display: flex;
+ align-items: center;
+ &:hover svg {
+ fill: var(--highlight);
+ transform: rotate(90deg);
+ }
+ }
+
+ svg {
+ height: 18px;
+ width: 18px;
+ fill: var(--sub-text);
+ transition: fill 100ms, transform 300ms ease;
+ }
+ }
+}
diff --git a/src/popup/styles/InputArea.scss b/src/popup/styles/InputArea.scss
new file mode 100644
index 0000000..3700b4a
--- /dev/null
+++ b/src/popup/styles/InputArea.scss
@@ -0,0 +1,26 @@
+#inputArea {
+ margin: 10px;
+ textarea {
+ font: inherit;
+ resize: none;
+ overflow: auto;
+ background-color: var(--main-bg);
+
+ box-sizing: border-box;
+ width: 100%;
+ height: 60px;
+ max-height: 240px;
+ min-height: 60px;
+
+ margin: 0;
+ padding: 10px;
+ border: solid 1px var(--button);
+ border-radius: 2px;
+ transition: border-color 100ms ease-out;
+ }
+
+ textarea:hover,
+ textarea:focus {
+ border-color: var(--highlight);
+ }
+}
diff --git a/src/popup/styles/PopupPage.scss b/src/popup/styles/PopupPage.scss
new file mode 100644
index 0000000..bfbab9f
--- /dev/null
+++ b/src/popup/styles/PopupPage.scss
@@ -0,0 +1,38 @@
+body {
+ margin: 0;
+ font-family: "Segoe UI", "San Francisco", "Ubuntu", "Fira Sans", "Roboto", "Arial", "Helvetica",
+ sans-serif;
+ font-size: 13px;
+ width: 348px;
+ overflow: hidden;
+ background-color: var(--main-bg);
+
+ #root {
+ height: 100%;
+ }
+
+ hr {
+ border: none;
+ border-top: solid 1px var(--button);
+ height: 1px;
+ margin: 0px 10px;
+ }
+
+ ::-moz-selection {
+ background: var(--line);
+ }
+}
+
+:root {
+ --main-text: #0c0c0d;
+ --sub-text: #737373;
+ --line: #ededf0;
+ --button: #d7d7db;
+ --highlight: #5595ff;
+ --main-bg: #ffffff;
+ --confirm: #ff4f4f;
+ --error: #d70022;
+ --warn: #ff8f00;
+ --success: #058b00;
+ --info: #0a84ff;
+}
diff --git a/src/popup/styles/ResultArea.scss b/src/popup/styles/ResultArea.scss
new file mode 100644
index 0000000..8f51b59
--- /dev/null
+++ b/src/popup/styles/ResultArea.scss
@@ -0,0 +1,25 @@
+#resultArea {
+ max-height: 240px;
+ min-height: 30px;
+ overflow-y: auto;
+ word-wrap: break-word;
+ background-color: var(--main-bg);
+ margin: 10px;
+
+ p {
+ margin: 0;
+ padding: 0px 5px;
+ background-color: var(--main-bg);
+
+ &.resultText {
+ color: var(--main-text);
+ }
+ &.candidateText {
+ color: var(--sub-text);
+ margin-top: 1em;
+ &:empty {
+ margin-top: 0;
+ }
+ }
+ }
+}