Replace option page with React

This commit is contained in:
sienori 2019-02-20 20:36:57 +09:00
parent 24d44ef384
commit fcf20ab530
31 changed files with 1646 additions and 1348 deletions

22
package-lock.json generated
View file

@ -2970,8 +2970,7 @@
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"dev": true
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
},
"define-properties": {
"version": "1.1.3",
@ -5173,6 +5172,11 @@
"integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=",
"dev": true
},
"loglevel": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz",
"integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po="
},
"long": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
@ -8017,6 +8021,15 @@
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
"dev": true
},
"query-string": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-6.2.0.tgz",
"integrity": "sha512-5wupExkIt8RYL4h/FE+WTg3JHk62e6fFPWtAZA9J5IWK1PfTfKkMS93HBUHcFpeYi9KsY5pFbh+ldvEyaz5MyA==",
"requires": {
"decode-uri-component": "^0.2.0",
"strict-uri-encode": "^2.0.0"
}
},
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
@ -9158,6 +9171,11 @@
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
"dev": true
},
"strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",

View file

@ -3,6 +3,8 @@
"version": "0.0.0",
"dependencies": {
"browser-info": "^1.2.0",
"loglevel": "^1.6.1",
"query-string": "^6.1.0",
"react": "^16.4.0",
"react-dom": "^16.4.0",
"react-router": "^4.3.1",

View file

@ -19,8 +19,7 @@
"message": "Target language"
},
"langList": {
"message":
"af:Afrikaans, sq:Albanian, am:Amharic, ar:Arabic, hy:Armenian, az:Azerbaijani, eu:Basque, be:Belarusian, bn:Bengali, bs:Bosnian, bg:Bulgarian, ca:Catalan, ceb:Cebuano, ny:Chewa, zh-CN:Chinese (PRC), zh-TW:Chinese (Taiwan), co:Corsican, hr:Croatian, cs:Czech, da:Danish, nl:Dutch, en:English, eo:Esperanto, et:Estonian, fi:Finnish, fr:French, fy:Frisian, gl:Galician, ka:Georgian, de:German, el:Greek, gu:Gujarati, ht:Haitian, ha:Hausa, haw:Hawaiian, he:Hebrew, hi:Hindi, hu:Hungarian, is:Icelandic, ig:Igbo, id:Indonesian, ga:Irish, it:Italian, ja:Japanese, jv:Javanese, kn:Kannada, kk:Kazakh, km:Khmer, ky:Kirghiz, ko:Korean, ku:Kurdish, lo:Laotian, la:Latin, lv:Latvian, lt:Lithuanian, lb:Luxembourgish, mk:Macedonian, mg:Malagasy, ms:Malay, ml:Malayalam, mt:Maltese, mi:Maori, mr:Marathi, mn:Mongolian, hmn:Monk, my:Myanmar, ne:Nepali, no:Norwegian, fa:Persian, pl:Polish, pt:Portuguese, pa:Punjabi, ps:Pushto, ro:Romanian, ru:Russian, sm:Samoan, gd:Scottish Gaelic, sr:Serbian, sn:Shona, sd:Sindhi, si:Sinhala, sk:Slovak, sl:Slovenian, so:Somali, sx:Sotho, es:Spanish, su:Sundanese, sw:Swahili, sv:Swedish, tl:Tagalog, tg:Tajiki, ta:Tamil, te:Telugu, th:Thai, tr:Turkish, uk:Ukrainian, ur:Urdu, uz:Uzbek, vi:Vietnamese, cy:Welsh, xh:Xosa, yi:Yiddish, yo:Yoruba, zu:Zulu"
"message": "af:Afrikaans, sq:Albanian, am:Amharic, ar:Arabic, hy:Armenian, az:Azerbaijani, eu:Basque, be:Belarusian, bn:Bengali, bs:Bosnian, bg:Bulgarian, ca:Catalan, ceb:Cebuano, ny:Chewa, zh-CN:Chinese (PRC), zh-TW:Chinese (Taiwan), co:Corsican, hr:Croatian, cs:Czech, da:Danish, nl:Dutch, en:English, eo:Esperanto, et:Estonian, fi:Finnish, fr:French, fy:Frisian, gl:Galician, ka:Georgian, de:German, el:Greek, gu:Gujarati, ht:Haitian, ha:Hausa, haw:Hawaiian, he:Hebrew, hi:Hindi, hu:Hungarian, is:Icelandic, ig:Igbo, id:Indonesian, ga:Irish, it:Italian, ja:Japanese, jv:Javanese, kn:Kannada, kk:Kazakh, km:Khmer, ky:Kirghiz, ko:Korean, ku:Kurdish, lo:Laotian, la:Latin, lv:Latvian, lt:Lithuanian, lb:Luxembourgish, mk:Macedonian, mg:Malagasy, ms:Malay, ml:Malayalam, mt:Maltese, mi:Maori, mr:Marathi, mn:Mongolian, hmn:Monk, my:Myanmar, ne:Nepali, no:Norwegian, fa:Persian, pl:Polish, pt:Portuguese, pa:Punjabi, ps:Pushto, ro:Romanian, ru:Russian, sm:Samoan, gd:Scottish Gaelic, sr:Serbian, sn:Shona, sd:Sindhi, si:Sinhala, sk:Slovak, sl:Slovenian, so:Somali, sx:Sotho, es:Spanish, su:Sundanese, sw:Swahili, sv:Swedish, tl:Tagalog, tg:Tajiki, ta:Tamil, te:Telugu, th:Thai, tr:Turkish, uk:Ukrainian, ur:Urdu, uz:Uzbek, vi:Vietnamese, cy:Welsh, xh:Xosa, yi:Yiddish, yo:Yoruba, zu:Zulu"
},
"settingsLabel": {
@ -66,8 +65,7 @@
"message": "Do not display the button if translation is not required"
},
"ifCheckLangCaptionLabel": {
"message":
"Detects the language of the selected text, and if it is the same as the target language, the button is not displayed."
"message": "Detects the language of the selected text, and if it is the same as the target language, the button is not displayed."
},
"toolbarLabel": {
@ -77,8 +75,7 @@
"message": "Automatically switch to the second language"
},
"ifChangeSecondLangCaptionLabel": {
"message":
"Detects the language of the input text, and if it is the same as the default target language, translate it into the second language."
"message": "Detects the language of the input text, and if it is the same as the default target language, translate it into the second language."
},
"secondTargetLangLabel": {
"message": "Second language"
@ -90,8 +87,7 @@
"message": "Waiting time to translate"
},
"waitTimeCaptionLabel": {
"message":
"Specify the waiting time from the input of a character to the start of translation. (millisecond)"
"message": "Specify the waiting time from the input of a character to the start of translation. (millisecond)"
},
"waitTime2CaptionLabel": {
"message": "If you translate it many times in a short time, it may become unusable for a while."
@ -104,8 +100,7 @@
"message": "Display the context menu"
},
"ifShowMenuCaptionLabel": {
"message":
"Add items to the context menu displayed when right clicking on the web page or the tab."
"message": "Add items to the context menu displayed when right clicking on the web page or the tab."
},
"styleLabel": {
@ -160,6 +155,59 @@
"message": "Background color"
},
"otherLabel": {
"message": "Other"
},
"isShowOptionsPageWhenUpdatedLabel": {
"message": "Display option page when updating"
},
"isShowOptionsPageWhenUpdatedCaptionLabel": {
"message": "Display the options page when Simple Translate is updated. You can know the update contents quickly."
},
"isDebugModeLabel": {
"message": "Enable debug mode"
},
"isDebugModeCaptionLabel": {
"message": "When debug mode is enabled, the log is output to the debugger."
},
"resetSettingsLabel": {
"message": "Reset settings"
},
"resetSettingsCaptionLabel": {
"message": "Restore all settings to default."
},
"resetSettingsButtonLabel": {
"message": "Reset"
},
"shortcutsLabel": {
"message": "Shortcuts"
},
"keyboardShortcutsLabel": {
"message": "Keyboard shortcuts"
},
"setKeyboardShortCutsMessage": {
"message": "Set keyboard shortcuts."
},
"typeShortcutMessage": {
"message": "Type a shortcut"
},
"typeLetterMessage": {
"message": "Type a letter"
},
"includeModifierKeysMessage": {
"message": "Include either Ctrl or Alt"
},
"includeMacModifierKeysMessage": {
"message": "Include either Command, Ctrl or Alt"
},
"invalidLetterMessage": {
"message": "The letter can not be used"
},
"invalidShortcutMessage": {
"message": "The shortcut can not be used"
},
"informationLabel": {
"message": "Information"
},
@ -170,8 +218,7 @@
"message": "Please make a donation"
},
"donationCaptionLabel": {
"message":
"Thank you for using Simple Translate.<br>Your support will be a big encouragement, as I continue to develop the add-on.<br>If you like Simple Translate, I would be pleased if you could consider donating."
"message": "Thank you for using Simple Translate.<br>Your support will be a big encouragement, as I continue to develop the add-on.<br>If you like Simple Translate, I would be pleased if you could consider donating."
},
"amazonTitleLabel": {
"message": "amazon.co.jp eGift Cards"

View file

@ -1,291 +0,0 @@
/* 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/. */
//初回起動時にオプションページを表示して設定を初期化
//Display option page at initial startup and initialize settings
/*
browser.runtime.onInstalled.addListener(function(){
browser.runtime.openOptionsPage();
});
*/
function settingsObj() {}
(function() {
//オプションページを書き換え,設定の初期化
//Rewrite option page, initialize setting
settingsObj.prototype.initOptionsPage = function() {
return new Promise(function(resolve, reject) {
labelSet();
getSettingsByHtml();
overRideSettingsByStorage().then(function() {
overRideHtml();
saveSettings();
resolve();
});
});
};
//オプションページから設定を保存
//Save settings from options page
settingsObj.prototype.saveOptionsPage = function() {
return new Promise(function(resolve, reject) {
getSettingsByHtml();
saveSettings().then(function() {
resolve();
});
});
};
//設定を初期化
//Initialize setting
settingsObj.prototype.init = function() {
return new Promise(function(resolve, reject) {
getSettings().then(function() {
resolve(Settings);
});
});
};
//設定を返す
//return settings
settingsObj.prototype.get = function() {
return Settings;
};
//受け取ったオブジェクトを保存
//Save the received object
settingsObj.prototype.save = function(settings) {
return new Promise(function(resolve, reject) {
for (let i in settings) {
Settings[i] = settings[i];
}
saveSettings().then(function() {
resolve();
});
});
};
//設定を削除
//Delete settings
settingsObj.prototype.clear = function(setting) {
return new Promise(function(resolve, reject) {
delete Settings[setting];
saveSettings().then(function() {
resolve();
});
});
};
//全ての設定を削除
//Delete all settings
settingsObj.prototype.clearAll = function() {
return new Promise(function(resolve, reject) {
Settings = new settingsObj();
saveSettings().then(function() {
resolve();
});
});
};
settingsObj.prototype.labelSet = function() {
labelSet();
};
//let Settings = new settingsObj();
let Settings = {};
//S = new settingsObj(); //外部から呼び出し Call from outside
//spanやoptionのidbuttonのclassに"Label"が含まれるときi18nから値を取得して書き換え
//When "label" is included in span and option id, button class Retrieve the value from i18n and rewrite it
function labelSet() {
textLabelSet("p");
textLabelSet("span");
textLabelSet("option");
textLabelSet("input");
function textLabelSet(tagName) {
const items = document.getElementsByTagName(tagName);
for (let i of items) {
let label;
if (i.id != undefined && i.id.includes("Label")) {
label = browser.i18n.getMessage(i.id);
} else if (i.className != undefined && i.className.includes("Label")) {
const labelList = i.className.split(" ").filter((element, index, array) => {
return element.includes("Label");
});
label = browser.i18n.getMessage(labelList[0]);
} else {
continue;
}
if (!label == "") {
if (tagName == "input") {
switch (i.type) {
case "button":
case "submit":
i.value = label;
break;
case "text":
i.placeholder = label;
break;
}
} else {
i.innerHTML = label;
}
}
}
}
}
//storageからSettingsの項目を取得して存在しない物をSettingsに上書き
//Retrieve the Settings item from storage and overwrite Settings that do not exist
function overRideSettingsByStorage() {
return new Promise(function(resolve, reject) {
browser.storage.local.get("Settings", function(value) {
for (let i in Settings) {
if (value.Settings != undefined && value.Settings[i] != undefined) {
Settings[i] = value.Settings[i];
}
}
for (let i in value.Settings) {
if (Settings[i] == undefined) Settings[i] = value.Settings[i];
}
resolve();
});
});
}
//オプションページにSettingsを反映
//Reflect Settings on option page
function overRideHtml() {
let inputs = document.getElementsByTagName("input");
for (let i in inputs) {
if (inputs[i].id == undefined) continue;
if (inputs[i].className != undefined && inputs[i].className.indexOf("noSetting") != -1)
continue;
switch (inputs[i].type) {
case "text":
case "number":
case "search":
case "tel":
case "url":
case "email":
case "password":
case "datetime":
case "month":
case "week":
case "time":
case "datetime-local":
case "range":
case "color":
inputs[i].value = Settings[inputs[i].id];
break;
case "checkbox":
inputs[i].checked = Settings[inputs[i].id];
break;
case "radio":
if (Settings[inputs[i].name] == inputs[i].value) {
inputs[i].checked = true;
}
break;
}
}
let textareas = document.getElementsByTagName("textarea");
for (let i in textareas) {
if (textareas[i].id == undefined) continue;
if (textareas[i].className != undefined && textareas[i].className.indexOf("noSetting") != -1)
continue;
textareas[i].value = Settings[textareas[i].id];
}
let selects = document.getElementsByTagName("select");
for (let i in selects) {
if (selects[i].id == undefined) continue;
if (selects[i].className != undefined && inputs[i].className.indexOf("noSetting") != -1)
continue;
selects[i].value = Settings[selects[i].id];
}
}
//オプションページから設定の値を取得
//Get setting value from option page
function getSettingsByHtml() {
let inputs = document.getElementsByTagName("input");
for (let i in inputs) {
if (inputs[i].id == undefined) continue;
if (inputs[i].className != undefined && inputs[i].className.indexOf("noSetting") != -1)
continue;
switch (inputs[i].type) {
case "text":
case "number":
case "search":
case "tel":
case "url":
case "email":
case "password":
case "datetime":
case "month":
case "week":
case "time":
case "datetime-local":
case "range":
case "color":
Settings[inputs[i].id] = inputs[i].value;
break;
case "checkbox":
Settings[inputs[i].id] = inputs[i].checked;
break;
case "radio":
if (inputs[i].checked == true) {
Settings[inputs[i].name] = inputs[i].value;
}
break;
}
}
let textareas = document.getElementsByTagName("textarea");
for (let i in textareas) {
if (textareas[i].id == undefined) continue;
if (textareas[i].className != undefined && textareas[i].className.indexOf("noSetting") != -1)
continue;
Settings[textareas[i].id] = textareas[i].value;
}
let selects = document.getElementsByTagName("select");
for (let i in selects) {
if (selects[i].id == undefined) continue;
if (selects[i].className != undefined && selects[i].className.indexOf("noSetting") != -1)
continue;
Settings[selects[i].id] = selects[i].value;
}
}
//ストレージが変更されたらget
browser.storage.onChanged.addListener(changedSettings);
function changedSettings(changes, area) {
if (Object.keys(changes).includes("Settings")) {
Settings = changes.Settings.newValue;
}
}
function getSettings() {
return new Promise(function(resolve, reject) {
browser.storage.local.get("Settings", function(value) {
Settings = value.Settings;
resolve(Settings);
});
});
}
function saveSettings() {
return new Promise(function(resolve, reject) {
browser.storage.local
.set({
Settings: Settings
})
.then(function() {
resolve(Settings);
});
});
}
})();

View file

@ -0,0 +1,12 @@
const alphabeticallySort = (a, b) => a.name.localeCompare(b.name);
export default () => {
const langListText = browser.i18n.getMessage("langList");
const langList = langListText.split(", ");
const langOptions = langList.map(lang => ({
value: lang.split(":")[0],
name: lang.split(":")[1]
}));
langOptions.sort(alphabeticallySort);
return langOptions;
};

23
src/common/getShortcut.js Normal file
View file

@ -0,0 +1,23 @@
import browserInfo from "browser-info";
import manifest from "src/manifest-firefox.json";
export default commandId => {
const suggestedKeys = manifest.commands[commandId].suggested_key || null;
if (!suggestedKeys) return null;
const os = browserInfo().os;
switch (os) {
case "Windows":
return suggestedKeys.windows || suggestedKeys.default;
case "OS X":
return suggestedKeys.mac || suggestedKeys.default;
case "Linux":
return suggestedKeys.linux || suggestedKeys.default;
case "Android":
return suggestedKeys.android || suggestedKeys.default;
case "iOS":
return suggestedKeys.ios || suggestedKeys.default;
default:
return suggestedKeys.default || null;
}
};

19
src/common/log.js Normal file
View file

@ -0,0 +1,19 @@
import log from "loglevel";
import { getSettings } from "src/settings/settings";
export const overWriteLogLevel = () => {
const originalFactory = log.methodFactory;
log.methodFactory = (methodName, logLevel, loggerName) => {
const rawMethod = originalFactory(methodName, logLevel, loggerName);
return (logDir, ...args) => {
rawMethod(`[${methodName}]`, `${logDir}:`, ...args);
};
};
};
export const updateLogLevel = () => {
const isDebugMode = getSettings("isDebugMode");
if (isDebugMode) log.enableAll();
else log.disableAll();
};

View file

@ -0,0 +1,35 @@
import React from "react";
import browser from "webextension-polyfill";
import OptionContainer from "./OptionContainer";
import "../styles/CategoryContainer.scss";
export default props => {
const { category, elements } = props;
return (
<li className="categoryContainer">
<fieldset>
<legend>
<p className="categoryTitle">
{category !== "" ? browser.i18n.getMessage(category) : ""}
</p>
</legend>
<ul className="categoryElements">
{elements.map((option, index) => (
<div key={index}>
<OptionContainer {...option}>
{option.hasOwnProperty("childElements") && (
<ul className="childElements">
{option.childElements.map((option, index) => (
<OptionContainer {...option} key={index} />
))}
</ul>
)}
</OptionContainer>
<hr />
</div>
))}
</ul>
</fieldset>
</li>
);
};

View file

@ -0,0 +1,20 @@
import React from "react";
import { Route, Switch } from "react-router-dom";
import browserInfo from "browser-info";
import SettingsPage from "./SettingsPage";
import KeyboardShortcutsPage from "./KeyboardShortcutsPage";
import InformationPage from "./InformationPage";
import "../styles/ContentsArea.scss";
const isValidShortcuts = browserInfo().name == "Firefox" && browserInfo().version >= 60;
export default () => (
<div className="contentsArea">
<Switch>
<Route path="/settings" component={SettingsPage} />
{isValidShortcuts && <Route path="/shortcuts" component={KeyboardShortcutsPage} />}
<Route path="/information" component={InformationPage} />
<Route component={SettingsPage} />
</Switch>
</div>
);

View file

@ -0,0 +1,99 @@
import React from "react";
import browser from "webextension-polyfill";
import browserInfo from "browser-info";
import queryString from "query-string";
import OptionsContainer from "./OptionContainer";
import manifest from "src/manifest-chrome.json";
export default props => {
const query = queryString.parse(props.location.search);
const extensionVersion = manifest.version;
const isChrome = browserInfo().name == "Chrome";
const paypalLink = `https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&no_shipping=1&business=sienori.firefox@gmail.com&item_name=Simple Translate ${
isChrome ? "for Chrome " : ""
}- Donation`;
const email = `sienori.firefox+st${isChrome ? "fc" : ""}@gmail.com`;
return (
<div>
<p className="contentTitle">{browser.i18n.getMessage("informationLabel")}</p>
<hr />
<OptionsContainer
title={"extName"}
captions={[]}
type={"none"}
updated={query.action === "updated"}
extraCaption={
<p className="caption">
<a href="https://github.com/sienori/simple-translate/releases" target="_blank">
Version {extensionVersion}
</a>
</p>
}
/>
<OptionsContainer
title={"licenseLabel"}
captions={["Mozilla Public License, Version. 2.0"]}
useRawCaptions={true}
type={"none"}
/>
<hr />
<OptionsContainer title={"donationLabel"} captions={["donationCaptionLabel"]} type={"none"} />
<OptionsContainer
title={""}
captions={[]}
type={"none"}
extraCaption={
<a href={paypalLink} target="_blank">
<img src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif" alt="Donate" />
</a>
}
/>
<OptionsContainer
title={""}
captions={[]}
type={"none"}
extraCaption={
<div>
<p className="caption">
<a className="amazonUrl" href={browser.i18n.getMessage("amazonUrl")} target="_blank">
{browser.i18n.getMessage("amazonTitleLabel")}
</a>
</p>
<p className="caption">email: {email}</p>
</div>
}
/>
<hr />
<OptionsContainer
title={""}
captions={[]}
type={"none"}
extraCaption={
<div>
<p>
<a
href={
browserInfo().name === "Firefox"
? "https://addons.mozilla.org/firefox/addon/simple-translate/?src=optionpage"
: "https://chrome.google.com/webstore/detail/simple-translate/xxxxx" //TODO: Chrome link
}
target="_blank"
>
{browserInfo().name === "Firefox"
? browser.i18n.getMessage("addonPageLabel")
: browser.i18n.getMessage("extensionPageLabel")}
</a>
<span> </span>
<a href="https://github.com/sienori/simple-translate" target="_blank">
GitHub
</a>
</p>
</div>
}
/>
</div>
);
};

View file

@ -0,0 +1,154 @@
import React, { Component } from "react";
import browserInfo from "browser-info";
import browser from "webextension-polyfill";
import ClearIcon from "../icons/clear.svg";
import RestetIcon from "../icons/reset.svg";
const normalizeKey = (key, keyCode) => {
const alphabet = /^([a-z]|[A-Z])$/;
if (alphabet.test(key)) return key.toUpperCase();
const digit = /^[0-9]$/;
const func = /^F([0-9]|1[0-2])$/;
const homes = /^(Home|End|PageUp|PageDown|Insert|Delete)$/;
if (digit.test(key) || func.test(key) || homes.test(key)) return key;
const space = /^\s$/;
if (space.test(key)) return "Space";
const arrows = /^(ArrowUp|ArrowDown|ArrowLeft|ArrowRight)$/;
if (arrows.test(key)) return key.split("Arrow")[1];
const medias = /^(MediaPlayPause|MediaStop)$/;
if (medias.test(key)) return key;
if (key == "MediaTrackNext") return "MediaNextTrack";
if (key == "MediaTrackPrevious") return "MediaPrevTrack";
const keyCode0 = 48;
if (keyCode0 <= keyCode && keyCode <= keyCode0 + 9) return keyCode - keyCode0;
if (keyCode == 188) return "Comma";
if (keyCode == 190) return "Period";
return "";
};
export default class KeyboardShortcutForm extends Component {
constructor(props) {
super(props);
this.isMac = browserInfo().os == "OS X";
this.state = {
shortcut: props.shortcut,
value: props.shortcut,
defaultValue: props.defaultValue,
error: ""
};
}
handleFocus(e) {
e.target.select();
window.document.onkeydown = () => false;
window.document.onkeypress = () => false;
}
handleBlur(e) {
const shortcut = this.state.shortcut;
this.setState({ error: "", value: shortcut });
window.document.onkeydown = () => true;
window.document.onkeypress = () => true;
}
handleChange(e) {}
handleKeyDown(e) {
if (e.repeat) return;
if (e.key == "Tab") {
window.document.activeElement.blur();
return;
}
const normalizedKey = normalizeKey(e.key, e.keyCode);
let error = "";
const mediaKeys = /^(MediaPlayPause|MediaStop|MediaNextTrack|MediaPrevTrack)$/;
const funcKeys = /^F([0-9]|1[0-2])$/;
const modifierKeys = /^(Control|Alt|Shift|Meta)$/;
if (mediaKeys.test(normalizedKey) || funcKeys.test(normalizedKey)) error = "";
else if (modifierKeys.test(e.key)) error = browser.i18n.getMessage("typeLetterMessage");
else if (!e.ctrlKey && !e.altKey && !e.metaKey)
error = this.isMac
? browser.i18n.getMessage("includeMacModifierKeysMessage")
: browser.i18n.getMessage("includeModifierKeysMessage");
else if (normalizedKey == "") error = browser.i18n.getMessage("invalidLetterMessage");
const value = `${e.ctrlKey ? (this.isMac ? "MacCtrl+" : "Ctrl+") : ""}${
e.metaKey && this.isMac ? "Command+" : ""
}${e.altKey ? "Alt+" : ""}${e.shiftKey ? "Shift+" : ""}${normalizedKey}`;
this.setState({ error: error, value: value || "" });
const isValidShortcut = value != "" && error == "";
if (isValidShortcut) this.updateShortcut(value);
}
handleKeyUp(e) {
if (this.state.error != "") {
this.setState({ value: "" });
}
}
async updateShortcut(shortcut) {
try {
await browser.commands.update({ name: this.props.id, shortcut: shortcut });
this.setState({ shortcut: shortcut || "" });
} catch (e) {
this.setState({ error: browser.i18n.getMessage("invalidShortcutMessage") });
}
}
async clearShortcut() {
await browser.commands.reset(this.props.id).catch(() => {});
this.setState({ shortcut: "", value: "" });
}
async resetShortcut() {
const defaultValue = this.state.defaultValue;
this.updateShortcut(defaultValue);
this.setState({ value: defaultValue || "" });
}
render() {
return (
<div className={`keyboardShortcut ${this.state.error && "isError"}`}>
<div className="row">
<input
type="text"
id={this.props.id}
value={this.state.value}
placeholder={browser.i18n.getMessage("typeShortcutMessage")}
onKeyDown={e => this.handleKeyDown(e)}
onKeyUp={e => this.handleKeyUp(e)}
onChange={e => this.handleChange(e)}
onFocus={e => this.handleFocus(e)}
onBlur={e => this.handleBlur(e)}
style={{ imeMode: "disabled" }}
/>
<button
className="clearButton"
title={browser.i18n.getMessage("clear")}
onClick={e => this.clearShortcut(e)}
>
<ClearIcon />
</button>
<button
className="resetButton"
title={browser.i18n.getMessage("reset")}
onClick={e => this.resetShortcut(e)}
>
<RestetIcon />
</button>
</div>
<p className="error">{this.state.error}</p>
</div>
);
}
}

View file

@ -0,0 +1,62 @@
import React, { Component } from "react";
import browser from "webextension-polyfill";
import getShortcut from "src/common/getShortcut";
import CategoryContainer from "./CategoryContainer";
export default class KeyboardShortcutPage extends Component {
constructor(props) {
super(props);
this.state = {
commands: [],
isInit: false
};
this.initCommands();
}
async initCommands() {
const commands = await browser.commands.getAll();
const rawDescription = /^__MSG_(.*)__$/;
const convertedCommands = commands.map(command => {
const isRawDescription = rawDescription.test(command.description);
if (isRawDescription)
command.description = browser.i18n.getMessage(command.description.match(rawDescription)[1]);
return command;
});
this.setState({ commands: convertedCommands, isInit: true });
}
render() {
const commandElements = this.state.commands.map(command => ({
id: command.name,
title: command.description,
useRawTitle: true,
captions: [],
type: "keyboard-shortcut",
shortcut: command.shortcut || "",
defaultValue: getShortcut(command.name)
}));
const shortcutCategory = {
category: "",
elements: [
{
id: "keyboard",
title: "keyboardShortcutsLabel",
captions: ["setKeyboardShortCutsMessage"],
type: "none",
new: true,
childElements: commandElements
}
]
};
return (
<div>
<p className="contentTitle">{browser.i18n.getMessage("keyboardShortcutsLabel")}</p>
<hr />
{this.state.isInit && <ul>{<CategoryContainer {...shortcutCategory} />}</ul>}
</div>
);
}
}

View file

@ -0,0 +1,182 @@
import React from "react";
import browser from "webextension-polyfill";
import { setSettings, getSettings } from "src/settings/settings";
import KeyboardShortcutForm from "./KeyboardShortcutForm";
import "../styles/OptionContainer.scss";
export default props => {
const { title, captions, type, id, children } = props;
const handleValueChange = e => {
let value = e.target.value;
if (type == "number") {
const validity = e.target.validity;
if (validity.rangeOverflow) value = props.max;
else if (validity.rangeUnderflow) value = props.min;
else if (validity.badInput || value == "" || !validity.valid) value = props.default;
}
setSettings(id, value);
};
const handleCheckedChange = e => {
setSettings(id, e.target.checked);
};
let formId;
let optionForm;
switch (type) {
case "checkbox":
formId = id;
optionForm = (
<label>
<input
type="checkbox"
id={formId}
onChange={handleCheckedChange}
defaultChecked={getSettings(id)}
/>
<span className="checkbox" />
</label>
);
break;
case "number":
formId = id;
optionForm = (
<input
type="number"
id={formId}
min={props.min}
max={props.max}
step={props.step}
placeholder={props.placeholder}
onChange={handleValueChange}
defaultValue={getSettings(id)}
/>
);
break;
case "text":
formId = id;
optionForm = (
<input
type="text"
id={formId}
placeholder={props.placeholder}
onChange={handleValueChange}
defaultValue={getSettings(id)}
/>
);
break;
case "radio":
formId = `${id}_${props.value}`;
optionForm = (
<label>
<input
type="radio"
id={formId}
name={id}
value={props.value}
onChange={handleValueChange}
defaultChecked={props.value === getSettings(id) ? "checked" : ""}
/>
<span className="radio" />
</label>
);
break;
case "color":
formId = id;
optionForm = (
<label>
<input
type="color"
id={formId}
onChange={handleValueChange}
defaultValue={getSettings(id)}
/>
</label>
);
break;
case "select":
formId = id;
optionForm = (
<div className="selectWrap">
<select id={formId} onChange={handleValueChange} defaultValue={getSettings(id)}>
{props.options.map((option, index) => (
<option value={option.value} key={index}>
{props.useRawOptionName ? option.name : browser.i18n.getMessage(option.name)}
</option>
))}
</select>
</div>
);
break;
case "button":
formId = "";
optionForm = (
<input type="button" value={browser.i18n.getMessage(props.value)} onClick={props.onClick} />
);
break;
case "file":
formId = "";
optionForm = (
<label className="button includeSpan" htmlFor={id}>
<span>{browser.i18n.getMessage(props.value)}</span>
<input
type="file"
id={id}
accept={props.accept}
multiple={props.multiple}
onChange={props.onChange}
/>
</label>
);
break;
case "keyboard-shortcut":
formId = id;
optionForm = (
<KeyboardShortcutForm id={id} shortcut={props.shortcut} defaultValue={props.defaultValue} />
);
break;
case "none":
formId = "";
optionForm = "";
break;
}
const shouldShow = props.shouldShow == undefined || props.shouldShow;
return (
shouldShow && (
<li className={`optionContainer ${props.updated ? "updated" : ""} ${props.new ? "new" : ""}`}>
{props.hr && <hr />}
<div className="optionElement">
<div className="optionText">
<label className="noHover" htmlFor={formId ? formId : null}>
<p>{title ? (props.useRawTitle ? title : browser.i18n.getMessage(title)) : ""}</p>
</label>
{captions.map((caption, index) => (
<p className="caption" key={index}>
{caption
? props.useRawCaptions
? caption
: browser.i18n.getMessage(caption).replace(/<br>/g, "\n")
: ""}
</p>
))}
{props.extraCaption ? props.extraCaption : ""}
</div>
<div className="optionForm">{optionForm}</div>
</div>
{children && (
<fieldset>
<legend className="noDisplayLegend">
{title ? (props.useRawTitle ? title : browser.i18n.getMessage(title)) : ""}
</legend>
{children}
</fieldset>
)}
</li>
)
);
};

View file

@ -0,0 +1,19 @@
import React from "react";
import { HashRouter } from "react-router-dom";
import SideBar from "./SideBar";
import ContentsArea from "./ContentsArea";
import ScrollToTop from "./ScrollToTop";
import "../styles/OptionsPage.scss";
export default () => {
return (
<HashRouter hashType="noslash">
<ScrollToTop>
<div className="optionsPage">
<SideBar />
<ContentsArea />
</div>
</ScrollToTop>
</HashRouter>
);
};

View file

@ -0,0 +1,16 @@
import { Component } from "react";
import { withRouter } from "react-router-dom";
class ScrollToTop extends Component {
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
window.scrollTo(0, 0);
}
}
render() {
return this.props.children;
}
}
export default withRouter(ScrollToTop);

View file

@ -0,0 +1,59 @@
import React, { Component } from "react";
import browser from "webextension-polyfill";
import { updateLogLevel, overWriteLogLevel } from "src/common/log";
import { initSettings, resetAllSettings } from "src/settings/settings";
import defaultSettings from "src/settings/defaultSettings";
import CategoryContainer from "./CategoryContainer";
export default class SettingsPage extends Component {
constructor(props) {
super(props);
this.state = {
isInit: false
};
this.init();
}
async init() {
await initSettings();
overWriteLogLevel();
updateLogLevel();
this.setState({ isInit: true });
}
render() {
const settingsContent = (
<ul>
{defaultSettings.map((category, index) => (
<CategoryContainer {...category} key={index} />
))}
<CategoryContainer {...additionalCategory} />
</ul>
);
return (
<div>
<p className="contentTitle">{browser.i18n.getMessage("settingsLabel")}</p>
<hr />
{this.state.isInit ? settingsContent : ""}
</div>
);
}
}
const additionalCategory = {
category: "",
elements: [
{
id: "resetSettings",
title: "resetSettingsLabel",
captions: ["resetSettingsCaptionLabel"],
type: "button",
value: "resetSettingsButtonLabel",
onClick: async () => {
await resetAllSettings();
location.reload(true);
}
}
]
};

View file

@ -0,0 +1,37 @@
import React from "react";
import browser from "webextension-polyfill";
import { Link, withRouter } from "react-router-dom";
import browserInfo from "browser-info";
import "../styles/SideBar.scss";
const isValidShortcuts = browserInfo().name == "Firefox" && browserInfo().version >= 60;
const SideBar = props => (
<div className="sideBar">
<div className="titleContainer">
<img src="/icons/64.png" className="logo" />
<span className="logoTitle">Simple Translate</span>
</div>
<ul>
<li
className={`sideBarItem ${
["/shortcuts", "/information"].every(path => path != props.location.pathname)
? "selected"
: ""
}`}
>
<Link to="/settings">{browser.i18n.getMessage("settingsLabel")}</Link>
</li>
{isValidShortcuts && (
<li className={`sideBarItem ${props.location.pathname == "/shortcuts" ? "selected" : ""}`}>
<Link to="/shortcuts">{browser.i18n.getMessage("shortcutsLabel")}</Link>
</li>
)}
<li className={`sideBarItem ${props.location.pathname == "/information" ? "selected" : ""}`}>
<Link to="/information">{browser.i18n.getMessage("informationLabel")}</Link>
</li>
</ul>
</div>
);
export default withRouter(SideBar);

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="480px" height="480px">
<path d="M12,2C6.47,2,2,6.47,2,12s4.47,10,10,10s10-4.47,10-10S17.53,2,12,2z M17,15.59L15.59,17L12,13.41L8.41,17L7,15.59 L10.59,12L7,8.41L8.41,7L12,10.59L15.59,7L17,8.41L13.41,12L17,15.59z"/>
</svg>

After

Width:  |  Height:  |  Size: 290 B

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="185.25 184.25 100 100" >
<path fill="none" stroke="#000000" stroke-width="8.5" stroke-miterlimit="10" d="M200.885,236.187 c-0.524-9.436,2.833-19.044,10.064-26.23c13.429-13.344,35.132-13.275,48.476,0.154l0.969,0.975"/>
<polygon points="272.125,195.804 245.038,222.749 272.051,222.817"/>
<path fill="none" stroke="#000000" stroke-width="8.5" stroke-miterlimit="10" d="M269.291,232.313 c0.524,9.436-2.833,19.044-10.064,26.23c-13.429,13.344-35.132,13.275-48.476-0.154l-0.969-0.975"/>
<polygon points="198.051,272.696 225.138,245.751 198.125,245.683"/>
</svg>

After

Width:  |  Height:  |  Size: 611 B

View file

@ -3,425 +3,13 @@
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="options.css">
<title>Simple Translate</title>
<link rel="icon" type="image/png" href="../icons/16.png" sizes="16x16">
<link rel="icon" type="image/png" href="../icons/32.png" sizes="32x32">
<link rel="icon" type="image/png" href="../icons/128.png" sizes="128x128">
<link rel="icon" type="image/png" href="../icons/64.png" sizes="64x64">
</head>
<body>
<div id=sidebar>
<ul>
<li>
<div class=titleContainer>
<img src="../icons/64.png" class=logo>
<span class=logotitle>Simple Translate</span>
</div>
</li>
<li>
<a href="#settings">
<span class="settingsLabel selected sidebarItem">設定</span>
</a>
</li>
<li>
<a href="#information">
<span class="informationLabel sidebarItem">情報</span>
</a>
</li>
</ul>
</div>
<div id=contents>
<div id=settings style="display:none">
<p class="contentTitle settingsLabel">設定</p>
<hr>
<ul>
<li class=categoryContainer>
<div class=categoryTitle>
<p class=generalLabel>全般</p>
</div>
<ul class=categoryElements>
<li class=optionContainer>
<div class=optionText>
<p class=targetLangLabel>翻訳先の言語</p>
<p class="caption targetLangCaptionLabel">デフォルトの翻訳先の言語を選択します。</p>
</div>
<div class=optionForm>
<div class=selectWrap>
<select id=targetLang class=saveByChange>
<option>日本語</option>
</select>
</div>
</div>
</li>
<li class=optionContainer>
<div class=optionText>
<p class=ifShowCandidateLabel>翻訳候補を表示する</p>
<p class="caption ifShowCandidateCaptionLabel">単一の語句が翻訳された時に複数の候補を表示します。</p>
</div>
<div class=optionForm>
<label>
<input type=checkbox id=ifShowCandidate class=saveByChange>
<span class=checkbox></span>
</label>
</div>
</li>
</ul>
</li>
<hr>
<li class=categoryContainer>
<div class=categoryTitle>
<p class=webPageLabel>webページ</p>
</div>
<ul class=categoryElements>
<li class=optionContainer>
<div class=optionText>
<p class=whenSelectTextLabel>テキスト選択時の動作</p>
</div>
</li>
<ul class=childElements>
<li class=optionContainer>
<div class=optionText>
<p class=ifShowButtonLabel>翻訳ボタンを表示する</p>
<p class="caption ifShowButtonCaptionLabel">クリックすると翻訳パネルが開くボタンを表示します。</p>
</div>
<div class=optionForm>
<label>
<input type=radio name=whenSelectText value=showButton class=saveByChange checked>
<span class=radio></span>
</label>
</div>
</li>
<li class="optionContainer">
<div class=optionText>
<p class=ifAutoTranslateLabel>翻訳パネルを表示する</p>
<p class="caption ifAutoTranslateCaptionLabel">ボタンを表示せずに直接翻訳パネルを表示します。</p>
</div>
<div class=optionForm>
<label>
<input type=radio name=whenSelectText value=showPanel class=saveByChange>
<span class=radio></span>
</label>
</div>
</li>
<li class=optionContainer>
<div class=optionText>
<p class=dontShowButtonLabel>ボタンやパネルを表示しない</p>
<p class="caption dontShowButtonCaptionLabel">翻訳ボタンや翻訳パネルを表示しません。</p>
</div>
<div class=optionForm>
<label>
<input type=radio name=whenSelectText value=dontShowButton class=saveByChange>
<span class=radio></span>
</label>
</div>
</li>
<hr>
<li class="optionContainer">
<div class=optionText>
<p class=ifCheckLangLabel>翻訳の必要がなければボタンを表示しない</p>
<p class="caption ifCheckLangCaptionLabel">選択したテキストの言語を判定し,翻訳先言語と同じ場合はボタンを表示しません。</p>
</div>
<div class=optionForm>
<label>
<input type=checkbox id=ifCheckLang class=saveByChange checked>
<span class=checkbox></span>
</label>
</div>
</li>
</ul>
</ul>
</li>
<hr>
<li class=categoryContainer>
<div class=categoryTitle>
<p class=toolbarLabel>ツールバーポップアップ</p>
</div>
<ul class=categoryElements>
<li class=optionContainer>
<div class=optionText>
<p class=ifChangeSecondLangLabel>自動的に第2言語に切り替える</p>
<p class="caption ifChangeSecondLangCaptionLabel">入力されたテキストの言語を判定しデフォルトの翻訳先言語と同じ場合には第2言語に翻訳します。</p>
</div>
<div class=optionForm>
<label>
<input type=checkbox id=ifChangeSecondLang class=saveByChange>
<span class=checkbox></span>
</label>
</div>
</li>
<ul class=childElements>
<li class=optionContainer>
<div class=optionText>
<p class=secondTargetLangLabel>第2言語</p>
<p class="caption secondTargetLangCaptionLabel">第2言語を指定します。</p>
</div>
<div class=optionForm>
<div class=selectWrap>
<select id=secondTargetLang class=saveByChange>
<option>日本語</option>
</select>
</div>
</div>
</li>
</ul>
<hr>
<li class="optionContainer">
<div class=optionText>
<p class=waitTimeLabel>翻訳までの待ち時間</p>
<p class="caption waitTimeCaptionLabel">文字が入力されてから翻訳を開始するまでの待ち時間を指定します。(ミリ秒)</p>
<p class="caption waitTime2CaptionLabel">短時間に何回も翻訳すると,しばらくの間利用できなくなることがあります。</p>
</div>
<div class=optionForm>
<label>
<input id=waitTime class=saveByChange type=number value=500 min=0 placeholder=500>
</label>
</div>
</li>
</ul>
</li>
<hr>
<li class=categoryContainer>
<div class=categoryTitle>
<p class=menuLabel>コンテキストメニュー</p>
</div>
<ul class=categoryElements>
<li class=optionContainer>
<div class=optionText>
<p class=ifShowMenuLabel>コンテキストメニューを表示する</p>
<p class="caption ifShowMenuCaptionLabel">ページやタブ上で右クリックした時に表示されるメニューに項目を追加します。</p>
</div>
<div class=optionForm>
<label>
<input type=checkbox id=ifShowMenu class=saveByChange checked>
<span class=checkbox></span>
</label>
</div>
</li>
</ul>
</li>
<hr>
<li class=categoryContainer>
<div class=categoryTitle>
<p class=styleLabel>スタイル</p>
</div>
<ul class=categoryElements>
<li class=optionContainer>
<div class=optionText>
<p class=buttonStyleLabel>翻訳ボタン</p>
<p class="caption buttonStyleCaptionLabel">webページ上で表示される翻訳ボタンのスタイルを指定します。</p>
</div>
</li>
<ul class=childElements>
<li class=optionContainer>
<div class=optionText>
<p class=buttonSizeLabel>サイズ</p>
</div>
<div class=optionForm>
<label>
<input id=buttonSize class=saveByChange type=number value=22 min=1 placeholder=22>
</label>
</div>
</li>
<li class=optionContainer>
<div class=optionText>
<p class=buttonPositionLabel>表示位置</p>
</div>
<div class=optionForm>
<div class=selectWrap>
<select id=buttonPosition class=saveByChange>
<option class=rightUpLabel value=rightUp>右上</option>
<option class=rightDownLabel value=rightDown selected>右下</option>
<option class=leftUpLabel value=leftUp>左上</option>
<option class=leftDownLabel value=leftDown>左下</option>
</select>
</div>
</div>
</li>
</ul>
<br>
<li class=optionContainer>
<div class=optionText>
<p class=panelStyleLabel>翻訳パネル</p>
<p class="caption panelStyleCaptionLabel">webページ上で表示される翻訳パネルのスタイルを指定します。</p>
</div>
</li>
<ul class=childElements>
<li class=optionContainer>
<div class=optionText>
<p class=widthLabel></p>
</div>
<div class=optionForm>
<label>
<input id=width class=saveByChange type=number value=300 min=1 placeholder=300>
</label>
</div>
</li>
<li class=optionContainer>
<div class=optionText>
<p class=heightLabel>高さ</p>
</div>
<div class=optionForm>
<label>
<input id=height class=saveByChange type=number value=200 min=1 placeholder=200>
</label>
</div>
</li>
<li class=optionContainer>
<div class=optionText>
<p class=fontSizeLabel>フォントサイズ</p>
</div>
<div class=optionForm>
<label>
<input id=fontSize class=saveByChange type=number value=13 min=1 placeholder=13>
</label>
</div>
</li>
<li class="optionContainer new">
<div class=optionText>
<p class=resultFontColorLabel>翻訳結果のフォントカラー</p>
</div>
<div class=optionForm>
<label>
<input id=resultFontColor class=saveByChange type=color value="#000000">
</label>
</div>
</li>
<li class="optionContainer new">
<div class=optionText>
<p class=candidateFontColorLabel>翻訳候補のフォントカラー</p>
</div>
<div class=optionForm>
<label>
<input id=candidateFontColor class=saveByChange type=color value="#737373">
</label>
</div>
</li>
<li class=optionContainer>
<div class=optionText>
<p class=bgColorLabel>背景色</p>
</div>
<div class=optionForm>
<label>
<input id=bgColor class=saveByChange type=color value="#ffffff">
</label>
</div>
</li>
</ul>
</ul>
</li>
</ul>
</div>
<div id=information style="display:none">
<p class="contentTitle informationLabel">情報</p>
<hr>
<ul>
<li>
<div class=optionContainer>
<div class="optionText addonVersion">
<p>Simple Translate</p>
<p class="caption">
<a class=pageLink href="https://github.com/sienori/simple-translate/releases" target="_blank">
Version</a>
</p>
</div>
</div>
</li>
<li>
<div class=optionContainer>
<div class=optionText>
<p class=licenseLabel>ライセンス</p>
<p class="caption">Mozilla Public License, Version. 2.0</p>
</div>
</div>
</li>
<hr>
<li>
<div class=optionContainer>
<div class="optionText">
<p class=donationLabel>ご寄付のお願い</p>
<p class="caption donationCaptionLabel">Simple Translateをご利用いただきありがとうございます。
<br>アドオンの開発を続けていく上で,皆様のご支援が大きな励みになります。
<br>もしあなたがSimple Translateを気に入ってくれたならご寄付をご検討いただけると幸いです。
</p>
</div>
</div>
</li>
<li>
<div class=optionContainer>
<div class=optionText>
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&no_shipping=1&business=sienori.firefox@gmail.com&item_name=SimpleTranslate - Donation"
target="_blank">
<img src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif" alt="Donate">
</a>
</div>
</div>
</li>
<li>
<div class=optionContainer>
<div class="optionText">
<a class=amazonUrl href="https://www.amazon.co.jp/dp/B004N3APGO?language=ja_JP" target="_blank">
<span class="amazonTitleLabel pageLink caption">amazonギフト券 Eメールタイプ</span>
</a>
<p class=caption>email: sienori.firefox+st@gmail.com</p>
</div>
</div>
</li>
<hr>
<li>
<div class=optionContainer>
<div class="optionText">
<a class="pageLink addonUrl" href="https://addons.mozilla.org/firefox/addon/simple-translate/" target="_blank">
<span class="addonPageLabel pageLink">アドオンページ</span>
</a>
<a class=pageLink href="https://github.com/sienori/simple-translate" target="_blank">
<span class=pageLink>github</span>
</a>
</div>
</div>
</li>
</ul>
</div>
</div>
<script type="text/javascript" src="../Settings.js"></script>
<script type="text/javascript" src="options.js"></script>
<script type="text/javascript" src="tm.hash-observer.js"></script>
<script type="text/javascript" src="ui.js"></script>
<div id=root />
</body>
</html>

View file

@ -1,69 +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/. */
const S = new settingsObj();
let targetLang = document.getElementById("targetLang");
let secondTargetLang = document.getElementById("secondTargetLang");
import React from "react";
import ReactDOM from "react-dom";
import OptionsPage from "./components/OptionsPage";
setLangList();
function setLangList() {
let langList = browser.i18n.getMessage("langList");
langList = langList.split(", ");
for (let i in langList) {
langList[i] = langList[i].split(":");
}
langList = langList.sort(alphabeticallySort);
let langListHtml = "";
for (let i of langList) {
langListHtml += `<option value=${i[0]}>${i[1]}</option>`;
}
targetLang.innerHTML = langListHtml;
secondTargetLang.innerHTML = langListHtml;
initialSetting();
}
function alphabeticallySort(a, b) {
if (a[1].toString() > b[1].toString()) {
return 1;
} else {
return -1;
}
}
function initialSetting() {
switch (
browser.i18n.getUILanguage() //一部の言語はブラウザの設定に合わせる
) {
case "ja":
case "zh-CN":
case "zh-TW":
case "ko":
case "ru":
case "de":
case "fr":
case "it":
targetLang.value = browser.i18n.getUILanguage();
secondTargetLang.value = "en";
break;
default:
targetLang.value = "en";
secondTargetLang.value = "ja";
break;
}
}
S.initOptionsPage().then(function() {
const saveByChangeItems = document.getElementsByClassName("saveByChange");
for (let item of saveByChangeItems) {
item.addEventListener("change", save);
}
});
function save() {
S.saveOptionsPage();
}
ReactDOM.render(<OptionsPage />, document.getElementById("root"));

View file

@ -1,415 +0,0 @@
:root {
--main-text: #0c0c0d;
--sub-text: #737373;
--line: #ededf0;
--button: #d7d7db;
--highlight: #5595ff;
--main-bg: #ffffff;
--new: #ff4f4f;
}
body {
font-family: "Segoe UI", "San Francisco", "Ubuntu", "Fira Sans", "Roboto", "Arial", "Helvetica",
sans-serif;
font-size: 15px;
font-weight: 400;
color: var(--main-text);
background-color: var(--main-bg);
line-height: 1.5;
display: flex;
flex-direction: row;
}
p {
margin: 0px;
}
ul {
padding: 0px;
}
li {
list-style-type: none;
}
hr {
width: 100%;
background-color: var(--line);
height: 1px;
border: none;
margin-top: 20px;
margin-bottom: 20px;
}
/*----sidebar----*/
#sidebar {
font-size: 17px;
font-weight: 400;
text-align: right;
flex-shrink: 0;
-moz-user-select: none;
}
.titleContainer {
display: flex;
flex-direction: column;
align-items: center;
}
.logo {
height: 64px;
width: 64px;
}
.logotitle {
text-align: left;
font-size: 14px;
font-weight: 300;
color: var(--sub-text);
/*margin: auto;*/
}
.sidebarItem:hover {
text-decoration-line: underline;
}
#sidebar > ul {
padding-left: 40px;
}
#sidebar > ul > li {
padding: 10px 15px;
}
#sidebar .selected {
color: var(--highlight);
}
/*----contents----*/
#contents {
padding-top: 20px;
padding-left: 20px;
padding-bottom: 50px;
width: 650px;
}
.contentTitle {
font-size: 33px;
font-weight: 200;
color: var(--sub-text);
line-height: 2;
}
.caption {
font-size: 13px;
font-weight: 400;
color: var(--sub-text);
}
#contents ul {
margin: 0px;
}
.childElements {
padding-left: 20px;
margin-bottom: 30px;
border-left: solid 10px var(--line);
}
.categoryContainer {
}
.categoryElements {
padding-left: 20px;
margin-bottom: 30px;
}
.categoryTitle {
font-size: 16px;
font-weight: 600;
color: var(--sub-text);
}
.optionContainer {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 10px 0px 10px 0px;
}
.optionContainer.reverse {
flex-direction: row-reverse;
}
.buttonsContainer {
justify-content: flex-start;
}
.optionText {
flex: 1;
}
.new p:nth-child(1)::after {
content: "New";
color: var(--new);
font-size: 14px;
border: 1px solid var(--new);
border-radius: 2px;
padding: 0px 5px;
margin-left: 5px;
}
.updated p:nth-child(1)::after {
content: "Updated";
color: var(--new);
font-size: 14px;
border: 1px solid var(--new);
border-radius: 2px;
padding: 0px 5px;
margin-left: 5px;
}
.optionForm {
display: flex;
align-items: center;
justify-content: flex-end;
margin-left: 10px;
}
.reverse .optionForm {
flex-basis: 40px;
justify-content: flex-start;
}
#importClear {
position: relative;
left: 10px;
}
.favicon {
width: 18px;
height: 18px;
padding: 1px;
display: block;
float: left;
}
/*----forms----*/
input {
font-family: inherit;
font-size: 14px;
}
input[type="number"],
input[type="text"],
input[type="color"] {
-moz-appearance: textfield;
width: 50px;
height: 30px;
padding-left: 5px;
padding-right: 5px;
border: 1px solid var(--button);
border-radius: 2px;
}
input[type="number"]:hover,
input[type="text"]:hover,
input[type="color"]:hover,
input[type="number"]:focus,
input[type="text"]:focus,
input[type="color"]:focus {
border-color: var(--highlight);
}
input[type="text"] {
width: 200px;
}
input[type="color"] {
background-color: var(--main-bg);
padding: 5px;
}
.button {
display: flex;
flex-direction: column;
justify-content: center;
min-width: 100px;
text-align: center;
padding: 5px;
height: 30px;
font-size: 13px;
color: var(--main-text);
border: 1px solid var(--button);
border-radius: 2px;
background-color: #fbfbfb;
cursor: pointer;
white-space: nowrap;
}
.includeSpan {
padding: 0px;
height: 28px;
}
.button:hover {
background: #f5f5f5;
border-color: var(--highlight);
}
::-moz-selection {
background: var(--line);
}
a:link {
color: var(--sub-text);
text-decoration-line: none;
}
a:visited {
color: var(--sub-text);
}
.pageLink {
color: var(--highlight) !important;
display: inline-block;
margin-right: 10px;
}
.pageLink:hover {
color: var(--highlight);
text-decoration-line: underline;
}
input[type="checkbox"] {
display: none;
}
.checkbox {
padding-left: 20px;
position: relative;
/*margin-right: 20px;*/
cursor: pointer;
}
.checkbox::before {
content: "";
display: block;
position: absolute;
top: 0;
left: -2px;
width: 20px;
height: 20px;
border: 1px solid var(--button);
border-radius: 2px;
}
.checkbox:hover::before {
border-color: var(--highlight);
}
input[type="checkbox"]:checked + .checkbox {
color: var(--highlight);
}
input[type="checkbox"]:checked + .checkbox::after {
content: "";
display: block;
position: absolute;
top: 1px;
left: 4px;
width: 6px;
height: 14px;
transform: rotate(40deg);
border-bottom: 3px solid var(--highlight);
border-right: 3px solid var(--highlight);
}
input[type="radio"] {
display: none;
}
.radio {
padding-left: 20px;
position: relative;
cursor: pointer;
}
.radio::before {
content: "";
display: block;
position: absolute;
top: 0;
left: -2px;
width: 20px;
height: 20px;
border: 1px solid var(--button);
border-radius: 50%;
}
.radio:hover::before {
border-color: var(--highlight);
}
input[type="radio"]:checked + .radio {
color: var(--highlight);
}
input[type="radio"]:checked + .radio::after {
content: "";
display: block;
position: absolute;
top: 6px;
left: 4px;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: var(--highlight);
}
select {
-moz-appearance: none;
text-overflow: ellipsis;
border: var(--button) solid 1px;
border-radius: 2px;
padding: 3px 5px;
padding-right: 20px;
width: 100%;
}
select:hover {
border: var(--highlight) solid 1px;
}
.selectWrap {
position: relative;
}
.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);
}
.selectWrap:hover::before {
border-bottom: 2px solid var(--highlight);
border-right: 2px solid var(--highlight);
}
option {
font-family: inherit;
font-size: 14px;
}

View file

@ -0,0 +1,15 @@
.categoryContainer {
list-style-type: none;
.categoryTitle {
font-size: 16px;
font-weight: 600;
color: var(--sub-text);
margin: 0;
}
.categoryElements {
padding-left: 20px;
margin-bottom: 30px;
}
}

View file

@ -0,0 +1,25 @@
.contentsArea {
margin-left: 150px;
width: 650px;
.contentTitle {
font-size: 33px;
font-weight: 200;
color: var(--sub-text);
line-height: 2;
margin: 0;
}
hr {
width: 100%;
background-color: var(--line);
height: 1px;
border: none;
margin-top: 20px;
margin-bottom: 20px;
}
ul {
padding: 0;
margin: 0;
}
}

View file

@ -0,0 +1,381 @@
.optionContainer {
list-style-type: none;
.optionElement {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 10px 0px 10px 0px;
.optionText {
flex: 1;
p {
margin: 0px;
}
.caption {
font-size: 13px;
font-weight: 400;
color: var(--sub-text);
white-space: pre-wrap;
}
a {
color: var(--highlight);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
.optionForm {
display: flex;
align-items: center;
justify-content: flex-end;
margin-left: 10px;
}
&.buttonsContainer {
justify-content: flex-start;
}
}
.childElements {
padding-left: 20px;
border-left: solid 10px var(--line);
}
}
fieldset {
border: none;
padding: 0;
margin: 0;
}
.noDisplayLegend {
width: 0;
height: 0;
overflow: hidden;
}
/*new, updated*/
.new > .optionElement p:nth-child(1)::after,
.updated > .optionElement p:nth-child(1)::after {
color: var(--new);
font-size: 14px;
border: 1px solid var(--new);
border-radius: 2px;
padding: 0px 5px;
margin-left: 5px;
}
.updated > .optionElement p:nth-child(1)::after {
content: "Updated";
}
.new > .optionElement p:nth-child(1)::after {
content: "New";
}
/*forms*/
input,
textarea {
font-family: inherit;
font-size: 14px;
}
textarea {
-moz-appearance: textfield;
border: 1px var(--button) solid;
border-radius: 2px;
padding-left: 5px;
padding-right: 5px;
padding: 5px;
width: calc(100% - 12px) !important;
height: 50px;
min-height: 50px;
&:hover,
&:focus {
border-color: var(--highlight);
}
}
input[type="number"],
input[type="text"],
input[type="color"] {
-moz-appearance: textfield;
width: 50px;
height: 30px;
padding-left: 5px;
padding-right: 5px;
border: 1px solid var(--button);
border-radius: 2px;
&:hover,
&:focus {
border-color: var(--highlight);
}
}
input[type="text"] {
width: 200px;
}
.noHover {
pointer-events: none;
}
/*checkbox*/
input[type="checkbox"] {
opacity: 0;
position: absolute;
&:checked + .checkbox {
color: var(--highlight);
&::after {
content: "";
display: block;
position: absolute;
top: 1px;
left: 4px;
width: 6px;
height: 14px;
transform: rotate(40deg);
border-bottom: 3px solid var(--highlight);
border-right: 3px solid var(--highlight);
}
}
&:focus + .checkbox::before {
border-color: var(--highlight);
}
}
.checkbox {
padding-left: 20px;
position: relative;
cursor: pointer;
&::before {
content: "";
display: block;
position: absolute;
top: 0;
left: -2px;
width: 20px;
height: 20px;
border: 1px solid var(--button);
border-radius: 2px;
}
&:hover::before {
border-color: var(--highlight);
}
}
/*radio*/
input[type="radio"] {
opacity: 0;
position: absolute;
&:checked + .radio {
color: var(--highlight);
}
&:checked + .radio::after {
content: "";
display: block;
position: absolute;
top: 6px;
left: 4px;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: var(--highlight);
}
&:focus + .radio::before {
border-color: var(--highlight);
}
}
.radio {
padding-left: 20px;
position: relative;
cursor: pointer;
&::before {
content: "";
display: block;
position: absolute;
top: 0;
left: -2px;
width: 20px;
height: 20px;
border: 1px solid var(--button);
border-radius: 50%;
}
&:hover::before {
border-color: var(--highlight);
}
}
/*color*/
input[type="color"] {
background-color: var(--main-bg);
padding: 5px;
}
/*select*/
select {
-moz-appearance: none;
-webkit-appearance: none;
text-overflow: ellipsis;
border: var(--button) solid 1px;
border-radius: 2px;
padding: 3px 5px;
padding-right: 20px;
width: 100%;
&:hover,
&:focus {
border: var(--highlight) solid 1px;
}
}
.selectWrap {
position: relative;
&: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);
}
&:hover::before {
border-bottom: 2px solid var(--highlight);
border-right: 2px solid var(--highlight);
}
}
option {
font-family: inherit;
font-size: 14px;
}
/*button*/
input[type="button"],
.button {
min-width: 100px;
text-align: center;
padding: 5px;
height: 30px;
font-size: 13px;
color: var(--main-text);
border: 1px solid var(--button);
border-radius: 2px;
background-color: #fbfbfb;
cursor: pointer;
white-space: nowrap;
&:hover,
&:focus {
background: #f5f5f5;
border-color: var(--highlight);
}
&.includeSpan {
padding: 0px;
height: 30px;
box-sizing: border-box;
span {
width: fit-content;
padding: 0px 5px;
}
}
}
.button {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
&:focus-within {
background: #f5f5f5;
border-color: var(--highlight);
}
}
input[type="file"] {
-moz-appearance: none;
-webkit-appearance: none;
opacity: 0;
width: 1px;
height: 0;
}
.keyboardShortcut {
position: relative;
.row {
display: flex;
align-items: center;
}
button {
position: absolute;
border: none;
padding: 0;
background-color: rgba(0, 0, 0, 0);
cursor: pointer;
height: 20px;
svg {
width: 20px;
height: 20px;
}
&.clearButton {
right: 0px;
margin: 3px;
svg {
fill: var(--sub-text);
}
&:hover svg {
fill: var(--new);
}
}
&.resetButton {
right: -20px;
margin: -3px;
svg {
fill: var(--sub-text);
path {
stroke: var(--sub-text);
}
}
&:hover svg {
fill: var(--highlight);
path {
stroke: var(--highlight);
}
}
}
}
input {
&::placeholder {
color: #fff;
}
&:focus::placeholder {
color: var(--main-text);
}
}
&.isError input {
border-color: var(--new);
}
.error {
position: absolute;
margin: 0;
width: 100%;
font-size: 12px;
color: var(--new);
text-align: center;
white-space: nowrap;
}
}

View file

@ -0,0 +1,27 @@
:root {
--main-text: #0c0c0d;
--sub-text: #737373;
--line: #ededf0;
--button: #d7d7db;
--highlight: #5595ff;
--main-bg: #ffffff;
--new: #ff4f4f;
}
.optionsPage {
font-family: "Segoe UI", "San Francisco", "Ubuntu", "Fira Sans", "Roboto", "Arial", "Helvetica",
sans-serif;
font-size: 15px;
font-weight: 400;
color: var(--main-text);
background-color: var(--main-bg);
line-height: 1.5;
margin: 20px 40px;
}
::-moz-selection {
background-color: var(--line);
}
::selection {
background-color: var(--line);
}

View file

@ -0,0 +1,53 @@
.sideBar {
position: fixed;
font-size: 17px;
font-weight: 400;
text-align: right;
-moz-user-select: none;
-webkit-user-select: none;
flex-shrink: 0;
.titleContainer {
display: flex;
flex-direction: column;
padding: 10px 15px;
align-items: center;
.logo {
height: 64px;
width: 64px;
margin-right: 5px;
}
.logoTitle {
text-align: left;
font-size: 14px;
font-weight: 300;
color: var(--sub-text);
margin: auto;
}
}
ul {
padding: 0;
margin: 0;
.sideBarItem {
list-style-type: none;
padding: 10px 15px;
:hover {
text-decoration-line: underline;
color: var(--highlight);
}
a {
color: var(--sub-text);
text-decoration-line: none;
}
&.selected a {
color: var(--highlight);
}
}
}
}

View file

@ -1,71 +0,0 @@
/**
* phi
*/
var tm = tm || {};
(function(){
tm.HashObserver = {};
tm.HashObserver.timerID = null;
tm.HashObserver.FPS = 100;
tm.HashObserver.enable = function()
{
var prevHash = window.location.hash;
tm.HashObserver.timerID = setInterval(function(){
if (prevHash != window.location.hash) {
var e = document.createEvent("HTMLEvents");
e.initEvent("changehash", true, false);
e.hash = window.location.hash;
e.prevHash = prevHash;
document.dispatchEvent(e);
}
prevHash = window.location.hash;
}, tm.HashObserver.FPS);
};
tm.HashObserver.disable = function()
{
clearInterval(tm.HashObserver.timerID);
};
})();
(function(){
tm.FormObserver = {};
tm.FormObserver.observe = function(elm, fps)
{
fps = fps || 100;
var prevValue = elm.value;
var timerID = null;
var observeForm = function() {
if (elm.value != prevValue) {
var e = document.createEvent("HTMLEvents");
e.initEvent("change", true, false);
elm.dispatchEvent(e);
}
prevValue = elm.value;
};
elm.addEventListener("focus", function() { timerID = setInterval(observeForm, fps); });
elm.addEventListener("blur", function() { clearInterval(timerID); });
};
tm.FormObserver.observeAll = function(className)
{
className = className || "tm-form-observer";
var targetList = document.getElementsByClassName(className);
for (var i=0,len=targetList.length; i<len; ++i) {
this.observe(targetList[i]);
}
}
})();

View file

@ -1,72 +0,0 @@
/* 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 e = {};
e.hash = location.href;
if (e.hash.indexOf("#") != -1) {
e.hash = "#" + e.hash.split("#")[1];
} else {
e.hash = "#settings";
}
readHash(e);
// hash の監視を開始
tm.HashObserver.enable();
document.addEventListener("changehash", readHash, false);
function readHash(e) {
const hash = e.hash.split("?")[0];
let selected = document.getElementsByClassName("selected");
selected[0].classList.remove("selected");
document.getElementById("settings").style.display = "none";
document.getElementById("information").style.display = "none";
switch (hash) {
case "#settings":
document.getElementById("settings").style.display = "block";
document.getElementsByClassName("settingsLabel")[0].classList.add("selected");
break;
case "#information":
document.getElementById("information").style.display = "block";
document.getElementsByClassName("informationLabel")[0].classList.add("selected");
break;
default:
document.getElementById("settings").style.display = "block";
document.getElementsByClassName("settingsLabel")[0].classList.add("selected");
break;
}
const params = getParams(e.hash);
switch (params.action) {
case "updated":
showUpdated();
break;
}
}
function getParams(hash) {
let params = {};
if (hash.split("?")[1] == undefined) return params;
hash = hash.split("?")[1].split("&");
for (let i of hash) {
params[i.split("=")[0]] = i.split("=")[1];
}
return params;
}
function showUpdated() {
const version = document.getElementsByClassName("addonVersion")[0];
version.classList.add("updated");
}
document.getElementsByClassName("addonUrl")[0].href = browser.i18n.getMessage("addonUrl");
document.getElementsByClassName("amazonUrl")[0].href = browser.i18n.getMessage("amazonUrl");
document
.getElementsByClassName("addonVersion")[0]
.getElementsByClassName("caption")[0]
.getElementsByTagName("a")[0].innerText = `Version ${browser.runtime.getManifest().version}`;

View file

@ -0,0 +1,250 @@
import genelateLangOptions from "src/common/genelateLangOptions";
const getDefaultLangs = () => {
const uiLang = browser.i18n.getUILanguage();
const langOptions = genelateLangOptions();
const shouldUseUiLang = langOptions.some(lang => lang.value == uiLang);
const targetLang = shouldUseUiLang ? uiLang : "en";
const secondTargetLang = targetLang === "en" ? "ja" : "en";
return { targetLang, secondTargetLang };
};
const langListOptions = genelateLangOptions();
const defaultLangs = getDefaultLangs();
export default [
{
category: "generalLabel",
elements: [
{
id: "targetLang",
title: "targetLangLabel",
captions: ["targetLangCaptionLabel"],
type: "select",
default: defaultLangs.targetLang,
options: langListOptions,
useRawOptionName: true
},
{
id: "ifShowCandidate",
title: "ifShowCandidateLabel",
captions: ["ifShowCandidateCaptionLabel"],
type: "checkbox",
default: true
}
]
},
{
category: "webPageLabel",
elements: [
{
id: "whenSelectText",
title: "whenSelectTextLabel",
captions: [],
type: "none",
default: "showButton",
childElements: [
{
id: "whenSelectText",
title: "ifShowButtonLabel",
captions: ["ifShowButtonCaptionLabel"],
type: "radio",
value: "showButton"
},
{
id: "whenSelectText",
title: "ifAutoTranslateLabel",
captions: ["ifAutoTranslateCaptionLabel"],
type: "radio",
value: "showPanel"
},
{
id: "whenSelectText",
title: "dontShowButtonLabel",
captions: ["dontShowButtonCaptionLabel"],
type: "radio",
value: "dontShowButton"
},
{
id: "ifCheckLang",
title: "ifCheckLangLabel",
captions: ["ifCheckLangCaptionLabel"],
type: "checkbox",
default: true,
hr: true
}
]
}
]
},
{
category: "toolbarLabel",
elements: [
{
id: "ifChangeSecondLang",
title: "ifChangeSecondLangLabel",
captions: ["ifChangeSecondLangCaptionLabel"],
type: "checkbox",
default: false,
childElements: [
{
id: "secondTargetLang",
title: "secondTargetLangLabel",
captions: ["secondTargetLangCaptionLabel"],
type: "select",
default: defaultLangs.secondTargetLang,
options: langListOptions,
useRawOptionName: true
}
]
},
{
id: "waitTime",
title: "waitTimeLabel",
captions: ["waitTimeCaptionLabel", "waitTime2CaptionLabel"],
type: "number",
min: 0,
placeholder: 500,
default: 500
}
]
},
{
category: "menuLabel",
elements: [
{
id: "ifShowMenu",
title: "ifShowMenuLabel",
captions: ["ifShowMenuCaptionLabel"],
type: "checkbox",
default: true
}
]
},
{
category: "styleLabel",
elements: [
{
title: "buttonStyleLabel",
captions: ["buttonStyleCaptionLabel"],
type: "none",
childElements: [
{
id: "buttonSize",
title: "buttonSizeLabel",
captions: [],
type: "number",
min: 1,
placeholder: 22,
default: 22
},
{
id: "buttonPosition",
title: "buttonPositionLabel",
captions: [],
type: "select",
default: "rightDown",
options: [
{
name: "rightUpLabel",
value: "rightUp"
},
{
name: "rightDownLabel",
value: "rightDown"
},
{
name: "leftUpLabel",
value: "leftUp"
},
{
name: "leftDownLabel",
value: "leftDown"
}
]
}
]
},
{
title: "panelStyleLabel",
captions: ["panelStyleCaptionLabel"],
type: "none",
childElements: [
{
id: "width",
title: "widthLabel",
captions: [],
type: "number",
min: 1,
placeholder: 300,
default: 300
},
{
id: "height",
title: "heightLabel",
captions: [],
type: "number",
min: 1,
placeholder: 200,
default: 200
},
{
id: "fontSize",
title: "fontSizeLabel",
captions: [],
type: "number",
min: 1,
placeholder: 13,
default: 13
},
{
id: "resultFontColor",
title: "resultFontColorLabel",
captions: [],
type: "color",
default: "#000000",
new: true
},
{
id: "candidateFontColor",
title: "candidateFontColorLabel",
captions: [],
type: "color",
default: "#737373",
new: true
},
{
id: "bgColor",
title: "bgColorLabel",
captions: [],
type: "color",
default: "#ffffff"
}
]
}
]
},
{
category: "otherLabel",
elements: [
{
id: "isShowOptionsPageWhenUpdated",
title: "isShowOptionsPageWhenUpdatedLabel",
captions: ["isShowOptionsPageWhenUpdatedCaptionLabel"],
type: "checkbox",
default: true,
new: true
},
{
id: "isDebugMode",
title: "isDebugModeLabel",
captions: ["isDebugModeCaptionLabel"],
type: "checkbox",
default: false,
new: true
}
]
}
];

59
src/settings/settings.js Normal file
View file

@ -0,0 +1,59 @@
import browser from "webextension-polyfill";
import log from "loglevel";
import defaultSettings from "./defaultSettings";
const logDir = "settings/settings";
let currentSettings = {};
export const initSettings = async () => {
const response = await browser.storage.local.get("Settings");
currentSettings = response.Settings || {};
let shouldSave = false;
const pushSettings = element => {
if (element.id == undefined || element.default == undefined) return;
if (currentSettings[element.id] == undefined) {
currentSettings[element.id] = element.default;
shouldSave = true;
}
};
const fetchDefaultSettings = () => {
defaultSettings.forEach(category => {
category.elements.forEach(optionElement => {
pushSettings(optionElement);
if (optionElement.childElements) {
optionElement.childElements.forEach(childElement => {
pushSettings(childElement);
});
}
});
});
};
fetchDefaultSettings();
if (shouldSave) await browser.storage.local.set({ Settings: currentSettings });
};
export const setSettings = async (id, value) => {
log.info(logDir, "setSettings()", id, value);
currentSettings[id] = value;
await browser.storage.local.set({ Settings: currentSettings });
};
export const getSettings = id => {
return currentSettings[id];
};
export const resetAllSettings = async () => {
log.info(logDir, "resetAllSettings()");
currentSettings = {};
await browser.storage.local.set({ Settings: currentSettings });
await initSettings();
};
export const handleSettingsChange = (changes, area) => {
if (Object.keys(changes).includes("Settings")) {
currentSettings = changes.Settings.newValue;
}
};