diff --git a/simple-translate/Settings.js b/simple-translate/Settings.js index 9d751f9..73dd918 100644 --- a/simple-translate/Settings.js +++ b/simple-translate/Settings.js @@ -81,50 +81,55 @@ function settingsObj() {}; }) }) } + settingsObj.prototype.labelSet = function () { + labelSet(); + } //let Settings = new settingsObj(); - let Settings={}; + let Settings = {}; //S = new settingsObj(); //外部から呼び出し Call from outside //spanやoptionのid,buttonのclassに"Label"が含まれるときi18nから値を取得して書き換え //When "label" is included in span and option id, button class Retrieve the value from i18n and rewrite it function labelSet() { - - //span idにLableが含まれていたら - let spans = document.getElementsByTagName("span"); - for (let i in spans) { - if (spans[i].id == undefined || spans[i].id.indexOf("Label") == -1) continue; - let label = browser.i18n.getMessage(spans[i].id); - if (label == "") continue; - spans[i].innerHTML = label; - } - - //button, submit, text classにLabelが含まれていたら - let inputs = document.getElementsByTagName("input"); - for (let i in inputs) { - if (inputs[i].id == undefined || inputs[i].className.indexOf("Label") == -1) continue; - let label=browser.i18n.getMessage(inputs[i].className); - if(label=="")continue; - - switch (inputs[i].type) { - case "button": - case "submit": - inputs[i].value = label; - break; - case "text": - inputs[i].placeholder=label; + 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; + } + } + } } - - //options idにLabelが含まれていたら - let options=document.getElementsByTagName("option"); - for(let i in options){ - if (options[i].id == undefined || options[i].id.indexOf("Label") == -1) continue; - let label=browser.i18n.getMessage(options[i].id); - if(label=="")continue; - - options[i].innerHTML=label; - } } //storageからSettingsの項目を取得して存在しない物をSettingsに上書き @@ -132,9 +137,9 @@ function settingsObj() {}; 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) { + if (value.Settings != undefined && value.Settings[i] != undefined) { Settings[i] = value.Settings[i]; } } @@ -151,7 +156,7 @@ function settingsObj() {}; function overRideHtml() { let inputs = document.getElementsByTagName("input"); for (let i in inputs) { - if(inputs[i].id==undefined) continue; + if (inputs[i].id == undefined) continue; if (inputs[i].className != undefined && inputs[i].className.indexOf("noSetting") != -1) continue; switch (inputs[i].type) { @@ -181,19 +186,19 @@ function settingsObj() {}; break; } } - let textareas=document.getElementsByTagName("textarea"); - for(let i in textareas){ - if(textareas[i].id==undefined) continue; + 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]; + textareas[i].value = Settings[textareas[i].id]; } - - let selects=document.getElementsByTagName("select"); - for(let i in selects){ - if(selects[i].id==undefined) continue; + + 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]; + + selects[i].value = Settings[selects[i].id]; } } @@ -203,7 +208,7 @@ function settingsObj() {}; let inputs = document.getElementsByTagName("input"); for (let i in inputs) { - if(inputs[i].id==undefined) continue; + if (inputs[i].id == undefined) continue; if (inputs[i].className != undefined && inputs[i].className.indexOf("noSetting") != -1) continue; switch (inputs[i].type) { @@ -233,25 +238,32 @@ function settingsObj() {}; break; } } - - let textareas=document.getElementsByTagName("textarea"); - for(let i in textareas){ - if(textareas[i].id==undefined) continue; + + 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; + Settings[textareas[i].id] = textareas[i].value; } - - let selects=document.getElementsByTagName("select"); - for(let i in selects){ - if(selects[i].id==undefined) continue; + + 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; + + Settings[selects[i].id] = selects[i].value; } } - + //ストレージが変更されたらget - browser.storage.onChanged.addListener(getSettings); + 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) { @@ -271,4 +283,4 @@ function settingsObj() {}; }) } -}()); \ No newline at end of file +}()); diff --git a/simple-translate/_locales/en/messages.json b/simple-translate/_locales/en/messages.json index ce9b8d0..50640fd 100644 --- a/simple-translate/_locales/en/messages.json +++ b/simple-translate/_locales/en/messages.json @@ -5,61 +5,135 @@ "extDescription": { "message": "View translations easily as you browse the web." }, - "initialTextArea":{ + "initialTextArea": { "message": "Enter text" }, - "showLink":{ + "showLink": { "message": "Translate this page" }, - "targetLangLabel":{ - "message":"Target language" + "targetLangLabel": { + "message": "Target language" }, - "langList":{ - "message":"" + "langList": { + "message": "" }, - "ifShowButtonLabel":{ - "message": "Pop up a button when selecting text" + "settingsLabel": { + "message": "Settings" }, - "ifCheckLangLabel":{ - "message": "Do not display the button if the selected text is the same as the target language" + "generalLabel": { + "message": "General" }, - "ifShowMenuLabel":{ - "message": "Display context menu" + "targetLangCaptionLabel": { + "message": "Select the default target language." }, - "ifChangeSecondLangLabel":{ - "message": "Switch to the second language if the input text of the tool bar translation panel is the same as the target language" + "webPageLabel": { + "message": "Web page" }, - "secondTargetLangLabel":{ + "ifShowButtonLabel": { + "message": "Display the button when text is selected" + }, + "ifShowButtonCaptionLabel": { + "message": "Display the translation button when text is selected on the web page." + }, + "ifCheckLangLabel": { + "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." + }, + "ifShowMenuLabel": { + "message": "Display the context menu" + }, + "ifShowMenuCaptionLabel": { + "message": "Add items to the context menu displayed when right clicking on the web page." + }, + + "toolbarLabel": { + "message": "Toolbar popup" + }, + "ifChangeSecondLangLabel": { + "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." + }, + "secondTargetLangLabel": { "message": "Second language" }, - "translatePageMenu":{ - "message": "Translate this page" + "secondTargetLangCaptionLabel": { + "message": "Select the second target language." }, - "translateTextMenu":{ - "message": "Translate selected text" + + "styleLabel": { + "message": "Style" }, - "translateLinkMenu":{ - "message": "Translate selected link" + "buttonStyleLabel": { + "message": "Translation button" }, - "panelSizeLabel":{ - "message": "Size of translation panel" + "buttonStyleCaptionLabel": { + "message": "Specify the style of the translation button displayed on the web page." }, - "buttonSizeLabel":{ - "message": "Size of button" + "buttonSizeLabel": { + "message": "Size" }, - "buttonPositionLabel":{ - "message": "Display position of button" + "buttonPositionLabel": { + "message": "Display position" }, - "rightUpLabel":{ - "message": "Upper right" + "rightUpLabel": { + "message": "Top right" }, - "rightDownLabel":{ + "rightDownLabel": { "message": "Bottom right" }, - "fontSizeLabel":{ + "leftUpLabel": { + "message": "Top left" + }, + "leftDownLabel": { + "message": "Bottom left" + }, + "panelStyleLabel": { + "message": "Translation panel" + }, + "panelStyleCaptionLabel": { + "message": "Specify the style of the translation panel displayed on the web page." + }, + "widthLabel": { + "message": "Width" + }, + "heightLabel": { + "message": "Height" + }, + "fontSizeLabel": { "message": "Font size" }, - "saveLabel":{ - "message": "Save" + + "informationLabel": { + "message": "Information" + }, + "licenseLabel": { + "message": "License" + }, + "donationLabel": { + "message": "Please make a donation" + }, + "donationCaptionLabel": { + "message": "Thank you for using Simple Translate.
Your support will be a big encouragement, as I continue to develop the add-on.
If you like Simple Translate, I would be pleased if you could consider donating." + }, + "addonPageLabel": { + "message": "Add-on pege" + }, + "addonUrl": { + "message": "https:\/\/addons.mozilla.org\/en-US\/firefox\/addon\/simple-translate\/?src=optionpage" + }, + + + "translatePageMenu": { + "message": "Translate this page" + }, + "translateTextMenu": { + "message": "Translate selected text" + }, + "translateLinkMenu": { + "message": "Translate selected link" } -} \ No newline at end of file +} diff --git a/simple-translate/_locales/ja/messages.json b/simple-translate/_locales/ja/messages.json index 8277a35..562100c 100644 --- a/simple-translate/_locales/ja/messages.json +++ b/simple-translate/_locales/ja/messages.json @@ -5,61 +5,136 @@ "extDescription": { "message": "シンプルな翻訳を表示するアドオンです。" }, - "initialTextArea":{ + "initialTextArea": { "message": "テキストを入力" }, - "showLink":{ + "showLink": { "message": "このページを翻訳" }, - "targetLangLabel":{ - "message":"翻訳先の言語" + "targetLangLabel": { + "message": "翻訳先の言語" }, - "langList":{ - "message":"" + "langList": { + "message": "" }, - "ifShowButtonLabel":{ + + "settingsLabel": { + "message": "設定" + }, + "generalLabel": { + "message": "全般" + }, + "targetLangCaptionLabel": { + "message": "デフォルトの翻訳先の言語を選択します。" + }, + "webPageLabel": { + "message": "webページ" + }, + "ifShowButtonLabel": { "message": "テキスト選択時にボタンを表示する" }, - "ifCheckLangLabel":{ - "message": "選択したテキストが翻訳先言語と同じ場合はボタンを表示しない" + "ifShowButtonCaptionLabel": { + "message": "webページ上のテキストを選択した時に翻訳ボタンを表示します。" }, - "ifShowMenuLabel":{ + "ifCheckLangLabel": { + "message": "翻訳の必要がなければボタンを表示しない" + }, + "ifCheckLangCaptionLabel": { + "message": "選択したテキストの言語を検出し,翻訳先言語と同じ場合はボタンを表示しません。" + }, + "ifShowMenuLabel": { "message": "コンテキストメニューを表示する" }, - "ifChangeSecondLangLabel":{ - "message": "ツールバー翻訳パネルの入力テキストが翻訳先言語と同じ場合は第二言語に切り替える" + "ifShowMenuCaptionLabel": { + "message": "ページ上で右クリックした時に表示されるコンテキストメニューに項目を追加します。" }, - "secondTargetLangLabel":{ + + "toolbarLabel": { + "message": "ツールバーポップアップ" + }, + "ifChangeSecondLangLabel": { + "message": "自動的に第二言語に切り替える" + }, + "ifChangeSecondLangCaptionLabel": { + "message": "入力されたテキストの言語を検出し,デフォルトの翻訳先言語と同じ場合には第二言語に翻訳します。" + }, + "secondTargetLangLabel": { "message": "第二言語" }, - "translatePageMenu":{ - "message": "ページ全体を翻訳" + "secondTargetLangCaptionLabel": { + "message": "第二言語を指定します。" }, - "translateTextMenu":{ - "message": "選択したテキストを翻訳" + + "styleLabel": { + "message": "スタイル" }, - "translateLinkMenu":{ - "message": "選択したリンクを翻訳" + "buttonStyleLabel": { + "message": "翻訳ボタン" }, - "panelSizeLabel":{ - "message": "翻訳パネルのサイズ" + "buttonStyleCaptionLabel": { + "message": "webページ上で表示される翻訳ボタンのスタイルを指定します。" }, - "buttonSizeLabel":{ - "message": "ボタンのサイズ" + "buttonSizeLabel": { + "message": "サイズ" }, - "buttonPositionLabel":{ - "message": "ボタンの表示位置" + "buttonPositionLabel": { + "message": "表示位置" }, - "rightUpLabel":{ + "rightUpLabel": { "message": "右上" }, - "rightDownLabel":{ + "rightDownLabel": { "message": "右下" }, - "fontSizeLabel":{ + "leftUpLabel": { + "message": "左上" + }, + "leftDownLabel": { + "message": "左下" + }, + "panelStyleLabel": { + "message": "翻訳パネル" + }, + "panelStyleCaptionLabel": { + "message": "webページ上で表示される翻訳パネルのスタイルを指定します。" + }, + "widthLabel": { + "message": "幅" + }, + "heightLabel": { + "message": "高さ" + }, + "fontSizeLabel": { "message": "フォントサイズ" }, - "saveLabel":{ - "message": "保存" + + "informationLabel": { + "message": "情報" + }, + "licenseLabel": { + "message": "ライセンス" + }, + "donationLabel": { + "message": "ご寄付のお願い" + }, + "donationCaptionLabel": { + "message": "Simple Translateをご利用いただきありがとうございます。
アドオンの開発を続けていく上で,皆様のご支援が大きな励みになります。
もしあなたがSimple Translateを気に入ってくれたなら,ご寄付をご検討いただけると幸いです。" + }, + "addonPageLabel": { + "message": "アドオンページ" + }, + "addonUrl": { + "message": "https:\/\/addons.mozilla.org\/ja\/firefox\/addon\/simple-translate\/?src=optionpage" + }, + + + "translatePageMenu": { + "message": "ページ全体を翻訳" + }, + "translateTextMenu": { + "message": "選択したテキストを翻訳" + }, + "translateLinkMenu": { + "message": "選択したリンクを翻訳" } -} \ No newline at end of file +} diff --git a/simple-translate/manifest.json b/simple-translate/manifest.json index d4db0fa..b49836f 100644 --- a/simple-translate/manifest.json +++ b/simple-translate/manifest.json @@ -15,7 +15,8 @@ "permissions": ["", "storage", "contextMenus", "clipboardRead"], "options_ui": { - "page": "options/options.html" + "page": "options/options.html", + "open_in_tab": true }, "icons": { diff --git a/simple-translate/options/options.css b/simple-translate/options/options.css index 1b4fbe7..9ed9228 100644 --- a/simple-translate/options/options.css +++ b/simple-translate/options/options.css @@ -1,26 +1,340 @@ -hr{ -width: 100%; -background-color: #d7d7db; -height: 1px; -border: none; +:root { + --main-text: #0c0c0d; + --sub-text: #737373; + --line: #ededf0; + --button: #d7d7db; + --highlight: #5595ff; + --main-bg: #ffffff; } -input[type="number"] { - -moz-appearance:textfield; + +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; +} + +.buttonsContainer { + justify-content: flex-start; +} + +.optionText { + flex: 1; +} + +.optionForm { + flex-basis: 150px; + display: flex; + align-items: center; + justify-content: flex-end; +} + +#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"] { + -moz-appearance: textfield; width: 50px; -} -#save{ - width: auto; height: 30px; - - margin-left: ; - margin-right: auto; - - - color: #111; - border: 1px solid #bbb; + padding-left: 5px; + padding-right: 5px; + border: 1px solid var(--button); + border-radius: 2px; +} + +input[type="number"]:hover, +input[type="text"]:hover, +input[type="number"]:focus, +input[type="text"]:focus { + border-color: var(--highlight); +} + +input[type="text"] { + width: 200px; +} + +.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; } -#save:hover{ + +.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); + 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); +} + +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; } diff --git a/simple-translate/options/options.html b/simple-translate/options/options.html index e0beb31..a1671f7 100644 --- a/simple-translate/options/options.html +++ b/simple-translate/options/options.html @@ -4,75 +4,304 @@ + Simple Translate + + + -

- -

-

- -

-

- -

-

- -

- -
-

- -

- -

- -

- -
-

- 翻訳パネルのサイズ - - -

- -

- -

- -

- ボタンの表示位置 - - -

- -

- -

- -
- - + +
+ + + +
+ + + + - \ No newline at end of file + diff --git a/simple-translate/options/options.js b/simple-translate/options/options.js index 144c12f..23a6279 100644 --- a/simple-translate/options/options.js +++ b/simple-translate/options/options.js @@ -1,17 +1,20 @@ /* 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 S = new settingsObj() +const S = new settingsObj() let targetLang = document.getElementById("targetLang"); let secondTargetLang = document.getElementById("secondTargetLang"); -targetLang.innerHTML=browser.i18n.getMessage("langList"); -secondTargetLang.innerHTML=browser.i18n.getMessage("langList"); +targetLang.innerHTML = browser.i18n.getMessage("langList"); +secondTargetLang.innerHTML = browser.i18n.getMessage("langList"); -S.initOptionsPage().then(function(){ - document.getElementById("save").addEventListener('click', function(){ - S.saveOptionsPage(); - }); +S.initOptionsPage().then(function () { + const saveByChangeItems = document.getElementsByClassName("saveByChange"); + for (let item of saveByChangeItems) { + item.addEventListener("change", save) + } }) +function save() { + S.saveOptionsPage(); +} diff --git a/simple-translate/options/tm.hash-observer.js b/simple-translate/options/tm.hash-observer.js new file mode 100644 index 0000000..c5210ab --- /dev/null +++ b/simple-translate/options/tm.hash-observer.js @@ -0,0 +1,71 @@ +/** +* 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