mirror of
https://github.com/chylex/Firefox-SCsCC.git
synced 2025-05-09 09:34:05 +02:00
initial commit
This commit is contained in:
commit
922099dc90
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
16
.eslintrc.json
Normal file
16
.eslintrc.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "airbnb-base/legacy",
|
||||
"env": {
|
||||
"browser": true,
|
||||
"webextensions": true
|
||||
},
|
||||
"globals": {
|
||||
"Promise": false
|
||||
},
|
||||
"rules": {
|
||||
"max-len": "off",
|
||||
"no-console": "off",
|
||||
"no-use-before-define": ["error", { "functions": false }],
|
||||
"vars-on-top": "off"
|
||||
}
|
||||
}
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
6
.tern-project
Normal file
6
.tern-project
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ecmaVersion": 5,
|
||||
"libs": [
|
||||
"browser"
|
||||
]
|
||||
}
|
339
LICENSE
Normal file
339
LICENSE
Normal file
@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{description}
|
||||
Copyright (C) {year} {fullname}
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
{signature of Ty Coon}, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
5
README.md
Normal file
5
README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# SCs Currency Converter
|
||||
|
||||
Currency Converter for Firefox
|
||||
|
||||
https://addons.mozilla.org/firefox/addon/scscurrencyconverter/
|
217
background.js
Normal file
217
background.js
Normal file
@ -0,0 +1,217 @@
|
||||
var manifest = chrome.runtime.getManifest();
|
||||
var preferences = { enabled: true };
|
||||
var currRates = {};
|
||||
var requests = {};
|
||||
var icons = {
|
||||
enabled: {
|
||||
16: chrome.runtime.getURL('icons/icon16.png'),
|
||||
32: chrome.runtime.getURL('icons/icon32.png'),
|
||||
48: chrome.runtime.getURL('icons/icon48.png')
|
||||
},
|
||||
disabled: {
|
||||
16: chrome.runtime.getURL('icons/icon16_off.png'),
|
||||
32: chrome.runtime.getURL('icons/icon32_off.png'),
|
||||
48: chrome.runtime.getURL('icons/icon48_off.png')
|
||||
}
|
||||
};
|
||||
|
||||
// set default preferences
|
||||
manifest.preferences.forEach(function callback(pref) {
|
||||
preferences[pref.name] = pref.value;
|
||||
});
|
||||
|
||||
// get storage
|
||||
chrome.storage.local.get(null, function callback(storage) {
|
||||
console.log('storage get', storage);
|
||||
|
||||
var newStorage = {};
|
||||
|
||||
if (storage.preferences && Object.keys(storage.preferences).length === Object.keys(preferences).length) {
|
||||
preferences = storage.preferences;
|
||||
} else {
|
||||
preferences = Object.assign({}, preferences, storage.preferences || {});
|
||||
newStorage.preferences = preferences;
|
||||
}
|
||||
|
||||
if (storage.currRates) {
|
||||
var newCurrRates = {};
|
||||
|
||||
Object.keys(storage.currRates).forEach(function cb(key) {
|
||||
var currRare = storage.currRates[key];
|
||||
if (Date.now() - currRare.updatedAt < 86400000) {
|
||||
newCurrRates[key] = currRare;
|
||||
}
|
||||
});
|
||||
|
||||
currRates = newCurrRates;
|
||||
if (Object.keys(storage.currRates).length !== Object.keys(newCurrRates).length) {
|
||||
newStorage.currRates = newCurrRates;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(newStorage).length) chrome.storage.local.set(newStorage);
|
||||
|
||||
if (!preferences.toCurr) chrome.runtime.openOptionsPage();
|
||||
|
||||
onPrefsChange();
|
||||
});
|
||||
|
||||
chrome.storage.onChanged.addListener(function listener(changes) {
|
||||
if (changes.preferences && changes.preferences.newValue) {
|
||||
preferences = changes.preferences.newValue;
|
||||
onPrefsChange();
|
||||
}
|
||||
if (changes.currRates && changes.currRates.newValue) {
|
||||
currRates = changes.currRates.newValue;
|
||||
onCurrRatesChange();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
chrome.runtime.onMessage.addListener(function listener(event, sender, sendResponse) {
|
||||
var type = event.type;
|
||||
var data = event.data;
|
||||
|
||||
if (type === 'getStorage') {
|
||||
return sendResponse({ preferences: preferences, currRates: currRates });
|
||||
}
|
||||
|
||||
if (type === 'getCurrRate') {
|
||||
getCurrRate(data.from, data.to).then(function then(currRate) { sendResponse(currRate); });
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
chrome.tabs.onActivated.addListener(function onActivated(activeInfo) {
|
||||
chrome.tabs.sendMessage(activeInfo.tabId, { preferences: preferences, currRates: currRates });
|
||||
});
|
||||
|
||||
|
||||
function onPrefsChange() {
|
||||
chrome.tabs.query({ active: true }, function callback(activeTabs) {
|
||||
activeTabs.forEach(function eachActiveTab(activeTab) {
|
||||
chrome.tabs.sendMessage(activeTab.id, { preferences: preferences });
|
||||
});
|
||||
});
|
||||
|
||||
chrome.browserAction.setIcon({ path: preferences.enabled ? icons.enabled : icons.disabled });
|
||||
}
|
||||
|
||||
function onCurrRatesChange() {
|
||||
chrome.tabs.query({ active: true }, function callback(activeTabs) {
|
||||
activeTabs.forEach(function eachActiveTab(activeTab) {
|
||||
chrome.tabs.sendMessage(activeTab.id, { currRates: currRates });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function getCurrRate(fromCurr, toCurr) {
|
||||
var reqKey = fromCurr + 'to' + toCurr;
|
||||
|
||||
// if a request for this exchange rate runs already, return it
|
||||
if (!requests[reqKey]) {
|
||||
requests[reqKey] = new Promise(function executor(resolve) {
|
||||
// if last request was within an hour, resolve
|
||||
if (currRates[reqKey] && currRates[reqKey].updatedAt && Date.now() - currRates[reqKey].updatedAt < 3600000) {
|
||||
resolve({ currRate: currRates[reqKey] });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('SCsCC - get ' + fromCurr + ' to ' + toCurr);
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
|
||||
var onEnd = function listener(event) {
|
||||
var request = event ? event.target : null;
|
||||
var currRate = reqComplete(request, fromCurr, toCurr);
|
||||
|
||||
resolve({ currRate: currRate });
|
||||
};
|
||||
|
||||
req.addEventListener('load', onEnd);
|
||||
req.addEventListener('error', onEnd);
|
||||
|
||||
req.open('GET', 'https://www.google.com/search?q=1+' + fromCurr + '+to+' + toCurr + '&hl=en', true);
|
||||
req.send();
|
||||
});
|
||||
}
|
||||
|
||||
return requests[reqKey]
|
||||
.then(function then(currRate) {
|
||||
requests[reqKey] = undefined;
|
||||
return currRate;
|
||||
});
|
||||
}
|
||||
|
||||
// on getCurrRate request complete
|
||||
function reqComplete(request, fromCurr, toCurr) {
|
||||
var reqKey = fromCurr + 'to' + toCurr;
|
||||
var currRate = currRates[reqKey] ? Object.assign({}, currRates[reqKey]) : {};
|
||||
|
||||
if (request && request.status === 200) {
|
||||
currRate.updatedAt = Date.now();
|
||||
|
||||
var txtMatch = request.responseText.match(/id=['"]?exchange_rate['"]?(?:\s+type=['"]?hidden['"]?)?\s+value=['"]?(\d+\.\d+)/i);
|
||||
|
||||
if (txtMatch && txtMatch[1]) {
|
||||
var newValue = parseFloat(txtMatch[1]);
|
||||
|
||||
if (isNaN(newValue)) { // if match is not a number
|
||||
console.log('SCsCC - got ' + fromCurr + ' to ' + toCurr + ' text, but not a number');
|
||||
} else if (newValue === currRate.value) { // if exchange rate didn't change (no refresh)
|
||||
console.log('SCsCC - got ' + fromCurr + ' to ' + toCurr + ', exchange rate didn\'t change',
|
||||
currRate.value,
|
||||
new Date(currRate.updatedAt).toUTCString());
|
||||
} else {
|
||||
console.log('SCsCC - got ' + fromCurr + ' to ' + toCurr + ': ' + currRate.value + ' -> ' + newValue,
|
||||
new Date(currRate.updatedAt).toUTCString());
|
||||
|
||||
showNotification(fromCurr, toCurr, newValue);
|
||||
|
||||
currRate.value = newValue;
|
||||
}
|
||||
} else {
|
||||
console.log('SCsCC - got text, but regex match failed');
|
||||
}
|
||||
} else {
|
||||
// will try again if requested after 10 minues
|
||||
currRate.updatedAt = Date.now() - 3000000;
|
||||
|
||||
if (request) console.log('SCsCC - get error:', request.statusText, request.status);
|
||||
else console.log('SCsCC - get error');
|
||||
}
|
||||
|
||||
if (!currRates[reqKey] || currRates[reqKey].value !== currRate.value || currRates[reqKey].updatedAt !== currRate.updatedAt) {
|
||||
var newCurrRates = Object.assign({}, currRates);
|
||||
newCurrRates[reqKey] = currRate;
|
||||
chrome.storage.local.set({ currRates: newCurrRates });
|
||||
}
|
||||
|
||||
return currRate;
|
||||
}
|
||||
|
||||
// show notification about exchange rate updates if enabled in preferences
|
||||
function showNotification(fromCurr, toCurr, newValue) {
|
||||
if (!preferences.noti) return;
|
||||
|
||||
var reqKey = fromCurr + 'to' + toCurr;
|
||||
var options = {
|
||||
type: 'basic',
|
||||
title: manifest.name,
|
||||
iconUrl: icons.enabled[48]
|
||||
};
|
||||
|
||||
if (currRates[reqKey]) { // on update
|
||||
options.message = fromCurr + ' to ' + toCurr + ' exchange rate updated:\n' +
|
||||
currRates[reqKey].value + ' → ' + newValue;
|
||||
} else { // on frist get
|
||||
options.message = fromCurr + ' → ' + toCurr + ' exchange rate got:\n' +
|
||||
newValue;
|
||||
}
|
||||
|
||||
chrome.notifications.create(options);
|
||||
}
|
542
content_scripts/scscc.js
Normal file
542
content_scripts/scscc.js
Normal file
@ -0,0 +1,542 @@
|
||||
window.SCSCC = window.SCSCC || {};
|
||||
|
||||
(function SCSCC() {
|
||||
var started = false;
|
||||
var preferences = {};
|
||||
var currRates = {};
|
||||
var requests = {};
|
||||
var observer;
|
||||
var styleElem;
|
||||
|
||||
var currPatts = [];
|
||||
var numPatt = '(((\\d{1,3}((,|\\.|\\s)\\d{3})+|(\\d+))((\\.|,)\\d{1,9})?)|(\\.\\d{1,9}))(,--)?';
|
||||
var symbPatts = {
|
||||
EUR: '(€|eur(os|o)?)',
|
||||
USD: '(\\$|usd)',
|
||||
GBP: '(£|gbp)'
|
||||
};
|
||||
|
||||
this.init = function init() {
|
||||
observer = new MutationObserver(checkMutations);
|
||||
|
||||
styleElem = document.createElement('style');
|
||||
styleElem.textContent =
|
||||
'data.scscc {\n' +
|
||||
' padding: 0 2px !important;\n' +
|
||||
' color: inherit !important;\n' +
|
||||
' white-space: pre !important;\n' +
|
||||
' border-width: 0 1px !important;\n' +
|
||||
' border-style: dotted !important;\n' +
|
||||
' border-color: inherit !important;\n' +
|
||||
' cursor: help !important;\n' +
|
||||
'}\n' +
|
||||
'data.scscc:hover {\n' +
|
||||
' background-color: red !important;\n' +
|
||||
' color: white !important;\n' +
|
||||
'}';
|
||||
|
||||
// build currPatts
|
||||
Object.keys(symbPatts).forEach(function eachSymbPatt(fromCurr) {
|
||||
var beforePatt = new RegExp(symbPatts[fromCurr] + '\\s?' + numPatt, 'gi');
|
||||
var afterPatt = new RegExp(numPatt + '\\s?' + symbPatts[fromCurr], 'gi');
|
||||
|
||||
currPatts.push({ from: fromCurr, patt: beforePatt }, { from: fromCurr, patt: afterPatt });
|
||||
});
|
||||
|
||||
chrome.runtime.sendMessage({ type: 'getStorage' }, function callback(storage) {
|
||||
preferences = storage.preferences;
|
||||
currRates = storage.currRates;
|
||||
|
||||
if (preferences.enabled && preferences.toCurr) start();
|
||||
|
||||
chrome.runtime.onMessage.addListener(function onMessage(data) {
|
||||
console.log('onMessage', data);
|
||||
if (data.preferences) {
|
||||
var newPrefs = data.preferences;
|
||||
var prefsChanged = false;
|
||||
|
||||
Object.keys(newPrefs).forEach(function eachPrefName(prefName) {
|
||||
if (!prefsChanged && newPrefs[prefName] !== preferences[prefName]) prefsChanged = true;
|
||||
});
|
||||
|
||||
if (prefsChanged) refreshPrefs(newPrefs);
|
||||
}
|
||||
|
||||
if (data.currRates) {
|
||||
var newCurrRates = data.currRates;
|
||||
var currRatesChanged = Object.keys(newCurrRates).length !== Object.keys(currRates).length;
|
||||
|
||||
if (!currRatesChanged) {
|
||||
Object.keys(newCurrRates).forEach(function eachCurrKey(currKey) {
|
||||
if (!currRatesChanged && (!currRates[currKey] || newCurrRates[currKey].value !== currRates[currKey].value)) currRatesChanged = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (currRatesChanged) refreshCurrRates(newCurrRates);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function start() {
|
||||
if (started) return;
|
||||
|
||||
started = true;
|
||||
|
||||
// start the observer
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
// add style
|
||||
if (preferences.style && !styleElem.parentNode) document.head.appendChild(styleElem);
|
||||
|
||||
replacePrices();
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (!started) return;
|
||||
|
||||
started = false;
|
||||
|
||||
observer.disconnect();
|
||||
|
||||
if (styleElem.parentNode) styleElem.parentNode.removeChild(styleElem);
|
||||
|
||||
var dataNodes = document.querySelectorAll('data.scscc');
|
||||
dataNodes.forEach(function eachDataNode(dataNode) {
|
||||
var replTxtNode = document.createTextNode(dataNode.title);
|
||||
dataNode.parentNode.replaceChild(replTxtNode, dataNode);
|
||||
});
|
||||
}
|
||||
|
||||
function checkMutations(mutlist) {
|
||||
var isDataScscc = function isDataScscc(node) {
|
||||
return /^data$/i.test(node.nodeName) && node.className === 'scscc';
|
||||
};
|
||||
|
||||
mutlist.forEach(function eachMut(mut) {
|
||||
mut.addedNodes.forEach(function eachAddedNode(addedNode) {
|
||||
if (!addedNode.parentNode || isDataScscc(addedNode) || (addedNode.nodeType === 3 && isDataScscc(addedNode.parentNode))) return;
|
||||
|
||||
replacePrices(addedNode);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function refreshPrefs(newPrefs) {
|
||||
preferences = newPrefs;
|
||||
|
||||
if (!preferences.enabled || !preferences.toCurr) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!preferences.style && styleElem.parentNode) styleElem.parentNode.removeChild(styleElem);
|
||||
else if (preferences.style && !styleElem.parentNode) document.head.appendChild(styleElem);
|
||||
|
||||
if (started) refreshPrices();
|
||||
else start();
|
||||
}
|
||||
|
||||
function refreshCurrRates(newCurrRates) {
|
||||
console.log('refreshCurrRates');
|
||||
var hasNewCurrRate = Object.keys(newCurrRates).length > Object.keys(currRates).length;
|
||||
currRates = newCurrRates;
|
||||
|
||||
if (!started) return;
|
||||
|
||||
refreshPrices();
|
||||
if (hasNewCurrRate) replacePrices();
|
||||
}
|
||||
|
||||
|
||||
function replacePrices(elem) {
|
||||
var rootElem = elem || document.body;
|
||||
|
||||
var textNodes = getTextNodes(rootElem);
|
||||
if (!textNodes.length) return;
|
||||
|
||||
var priceMatches = getPriceMatches(textNodes);
|
||||
if (!priceMatches.length) return;
|
||||
|
||||
replacePriceMatches(priceMatches);
|
||||
}
|
||||
|
||||
function refreshPrices() {
|
||||
var dataNodes = document.querySelectorAll('data.scscc');
|
||||
|
||||
dataNodes.forEach(function eachDataNode(dataNode) {
|
||||
var fromCurr = dataNode.dataset.curr;
|
||||
var currRate = getCurrRate(fromCurr);
|
||||
var replTxtNode;
|
||||
|
||||
if (fromCurr === preferences.toCurr || !currRate) {
|
||||
replTxtNode = document.createTextNode(dataNode.title);
|
||||
dataNode.parentNode.replaceChild(replTxtNode, dataNode);
|
||||
} else {
|
||||
var newPrice = parseFloat(dataNode.value) * currRate;
|
||||
var replTxt = formatPrice(newPrice);
|
||||
|
||||
if (dataNode.textContent !== newPrice) {
|
||||
replTxtNode = document.createTextNode(replTxt);
|
||||
dataNode.replaceChild(replTxtNode, dataNode.firstChild);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// get all text nodes of a given node
|
||||
function getTextNodes(node) {
|
||||
var textNodes = [];
|
||||
var ignoreNodes = /^(script|style|pre)$/i;
|
||||
var patt = new RegExp(numPatt);
|
||||
|
||||
function getChildTextNodes(n) {
|
||||
if (ignoreNodes.test(n.nodeName) || n.className === 'scscc') return;
|
||||
|
||||
if (n.nodeType === 3 && patt.test(n.nodeValue)) {
|
||||
textNodes.push(n);
|
||||
} else if (n.nodeType !== 3) {
|
||||
n.childNodes.forEach(getChildTextNodes);
|
||||
}
|
||||
}
|
||||
|
||||
getChildTextNodes(node);
|
||||
|
||||
return textNodes;
|
||||
}
|
||||
|
||||
|
||||
// check if there is any pattern match in a text node and return the matches
|
||||
function getPriceMatches(textNodes) {
|
||||
var priceMatches = [];
|
||||
|
||||
textNodes.forEach(function eachTextNode(textNode) {
|
||||
var txt = textNode.nodeValue;
|
||||
var matches = {};
|
||||
|
||||
currPatts.forEach(function eachCurrPatt(currPatt) {
|
||||
if (currPatt.from === preferences.toCurr) return;
|
||||
|
||||
var match = txt.match(currPatt.patt);
|
||||
if (match) {
|
||||
matches[currPatt.from] = (matches[currPatt.from] || []).concat(match);
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(matches).length) {
|
||||
priceMatches.push({
|
||||
node: textNode,
|
||||
matches: matches
|
||||
});
|
||||
}
|
||||
// else {
|
||||
// var specMatches = checkSiblings(textNode);
|
||||
//
|
||||
// if (Object.keys(specMatches).length) {
|
||||
// priceMatches.push({
|
||||
// node: textNode,
|
||||
// matches: specMatches
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
});
|
||||
|
||||
return priceMatches;
|
||||
}
|
||||
|
||||
// check if currency symbol is in an other node
|
||||
// function checkSiblings(textNode) {
|
||||
// var chckTxt = {};
|
||||
// var matches = {};
|
||||
// var txt = textNode.nodeValue;
|
||||
//
|
||||
// var match = txt.match(new RegExp(numPatt, 'g'));
|
||||
// if (match.length !== 1 || match[0] !== txt.trim()) return matches;
|
||||
//
|
||||
// // check previous sibling of
|
||||
// if (textNode.previousSibling && textNode.previousSibling.lastChild && textNode.previousSibling.lastChild.nodeType === 3) {
|
||||
// // this node -> check sibling's last child
|
||||
// chckTxt.prev = textNode.previousSibling.lastChild.nodeValue.trim();
|
||||
// } else if (textNode.parentNode.previousSibling) {
|
||||
// // parent node
|
||||
// if (textNode.parentNode.previousSibling.nodeType === 3) {
|
||||
// // if text node
|
||||
// chckTxt.prev = textNode.parentNode.previousSibling.nodeValue.trim();
|
||||
// } else if (textNode.parentNode.previousSibling.lastChild && textNode.parentNode.previousSibling.lastChild.nodeType === 3) {
|
||||
// // if not text node -> check last child
|
||||
// chckTxt.prev = textNode.parentNode.previousSibling.lastChild.nodeValue.trim();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // check next sibling of
|
||||
// if (textNode.nextSibling && textNode.nextSibling.firstChild && textNode.nextSibling.firstChild.nodeType === 3) {
|
||||
// // this node -> check sibling's first child
|
||||
// chckTxt.next = textNode.nextSibling.firstChild.nodeValue.trim();
|
||||
// } else if (textNode.parentNode.nextSibling) {
|
||||
// // parent node
|
||||
// if (textNode.parentNode.nextSibling.nodeType === 3) {
|
||||
// // if text node
|
||||
// chckTxt.next = textNode.parentNode.nextSibling.nodeValue.trim();
|
||||
// } else if (textNode.parentNode.nextSibling.firstChild && textNode.parentNode.nextSibling.firstChild.nodeType === 3) {
|
||||
// // if not text node -> check first child
|
||||
// chckTxt.next = textNode.parentNode.nextSibling.firstChild.nodeValue.trim();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Object.keys(chckTxt).forEach(function eachPos(pos) {
|
||||
// if (!chckTxt[pos]) return;
|
||||
//
|
||||
// Object.keys(symbPatts).forEach(function eachFromCurr(fromCurr) {
|
||||
// if (fromCurr === preferences.toCurr) return;
|
||||
//
|
||||
// var symbPatt;
|
||||
// if (pos === 'prev') symbPatt = new RegExp(symbPatts[fromCurr] + '$', 'gi');
|
||||
// else symbPatt = new RegExp('^' + symbPatts[fromCurr], 'gi');
|
||||
//
|
||||
// if (symbPatt.test(chckTxt[pos])) {
|
||||
// matches[fromCurr] = match;
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// return matches;
|
||||
// }
|
||||
|
||||
|
||||
function replacePriceMatches(priceMatches) {
|
||||
priceMatches.forEach(function eachCurrMatch(currMatch) {
|
||||
var dataNodes = getDataNodes(currMatch.node, currMatch.matches);
|
||||
if (!dataNodes.length) return;
|
||||
|
||||
replaceText(currMatch.node, dataNodes);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// find and convert the prices in the text, and return them as data nodes
|
||||
function getDataNodes(node, matches) {
|
||||
var txt = node.nodeValue;
|
||||
var dataNodes = [];
|
||||
|
||||
Object.keys(matches).forEach(function eachFromCurr(fromCurr) {
|
||||
var currRate = getCurrRate(fromCurr);
|
||||
if (!currRate) return;
|
||||
|
||||
matches[fromCurr].forEach(function eachMatch(match) {
|
||||
var repl;
|
||||
var dataNode = document.createElement('data');
|
||||
dataNode.className = 'scscc';
|
||||
dataNode.dataset.curr = fromCurr;
|
||||
|
||||
if (txt.trim() !== match) {
|
||||
repl = checkSpecCases(txt, match, fromCurr);
|
||||
if (!repl) return;
|
||||
} else {
|
||||
repl = match;
|
||||
}
|
||||
|
||||
dataNode.title = repl;
|
||||
|
||||
repl = cleanPrice(repl);
|
||||
dataNode.value = repl;
|
||||
|
||||
repl = parseFloat(repl) * currRate;
|
||||
repl = formatPrice(repl);
|
||||
dataNode.textContent = repl;
|
||||
|
||||
dataNodes.push(dataNode);
|
||||
});
|
||||
});
|
||||
|
||||
return dataNodes;
|
||||
}
|
||||
|
||||
function getCurrRate(fromCurr) {
|
||||
var reqKey = fromCurr + 'to' + preferences.toCurr;
|
||||
var currRate = currRates[reqKey] ? currRates[reqKey].value : null;
|
||||
|
||||
if (!requests[reqKey]) {
|
||||
requests[reqKey] = true;
|
||||
|
||||
chrome.runtime.sendMessage({ type: 'getCurrRate', data: { from: fromCurr, to: preferences.toCurr } }, function callback() {
|
||||
requests[reqKey] = false;
|
||||
});
|
||||
}
|
||||
|
||||
return currRate;
|
||||
}
|
||||
|
||||
function checkSpecCases(txt, match, from) {
|
||||
var chckchar;
|
||||
var charind = txt.indexOf(match);
|
||||
var checkedMatch = match;
|
||||
|
||||
// skip other dollars
|
||||
// Australian (A$)
|
||||
// Barbadian (Bds$)
|
||||
// Belizean (BZ$)
|
||||
// Brunei (B$)
|
||||
// Canadian (CA$)
|
||||
// Cayman Islands (CI$)
|
||||
// East Caribbean (EC$)
|
||||
// Fiji (FJ$)
|
||||
// Guyanese (G$)
|
||||
// Hong Kong (HK$)
|
||||
// Jamaican (J$)
|
||||
// Liberian (L$ or LD$)
|
||||
// Namibian (N$)
|
||||
// New Zealand (NZ$)
|
||||
// Singaporean (S$)
|
||||
// Soloman Islands (SI$)
|
||||
// Taiwanese (NT$)
|
||||
// Trinidad and Tobago (TT$)
|
||||
// Tuvaluan (TV$)
|
||||
// Zimbabwean (Z$)
|
||||
// Chilean (CLP$)
|
||||
// Colombian (COL$)
|
||||
// Dominican (RD$)
|
||||
// Mexican (Mex$)
|
||||
// Nicaraguan córdoba (C$)
|
||||
// Brazilian real (R$)
|
||||
if (from === 'USD' && match.charAt(0) === '$') {
|
||||
chckchar = txt.charAt(charind - 1);
|
||||
if (
|
||||
/\w/.test(chckchar) &&
|
||||
/(A|Bds|BZ|B|CA|CI|EC|FJ|G|HK|J|L|LD|N|NZ|S|SI|NT|TT|TV|Z|CLP|COL|RD|Mex|C|R)$/.test(txt.slice(0, charind))
|
||||
) return null;
|
||||
}
|
||||
|
||||
// in case text is like: masseur 1234
|
||||
// or
|
||||
// in case text is like: 1234 europe
|
||||
var sind = match.search(/eur|usd|gbp/i);
|
||||
if (sind !== -1) {
|
||||
if (sind === 0) { // starts with eur(os)/usd/gbp
|
||||
// if there is any word character before it, skip it
|
||||
chckchar = txt.charAt(charind - 1);
|
||||
if (/\w/.test(chckchar)) return null;
|
||||
} else { // ends with eur(os)/usd/gbp
|
||||
// if there is any word character after it, skip it
|
||||
chckchar = txt.charAt(charind + match.length);
|
||||
if (/\w/.test(chckchar)) return null;
|
||||
}
|
||||
}
|
||||
|
||||
// in case text is like: somestring1 234 $
|
||||
if (match.charAt(0).search(/\d/) !== -1) {
|
||||
// if there is a word character before it
|
||||
chckchar = txt.charAt(charind - 1);
|
||||
if (chckchar.search(/\w/) !== -1) {
|
||||
checkedMatch = match.replace(/^\d+\s/, ''); // convert only 234 $
|
||||
}
|
||||
}
|
||||
|
||||
return checkedMatch;
|
||||
}
|
||||
|
||||
// make price computer-readable: remove price symbols, and replace/remove separators
|
||||
function cleanPrice(repl) {
|
||||
// remove currency symbols and spaces
|
||||
var cleanedPrice = repl.replace(/€|eur(os|o)?|\$|usd|£|gbp|,--|\s/ig, '');
|
||||
|
||||
// if no decimal separator
|
||||
// remove possible "." or "," thousand separators
|
||||
if (cleanedPrice.search(/(\.|,)\d{1,2}$/) === -1) cleanedPrice = cleanedPrice.replace(/\.|,/g, '');
|
||||
// if decimal separator is "."
|
||||
// remove possible "," thousand separators
|
||||
else if (repl.search(/\.\d{1,2}$/) !== -1) cleanedPrice = cleanedPrice.replace(/,/g, '');
|
||||
// if decimal separptor is ","
|
||||
else {
|
||||
// remove possible "." thousand separators
|
||||
cleanedPrice = cleanedPrice.replace(/\./g, '');
|
||||
// replace dec separator to "."
|
||||
cleanedPrice = cleanedPrice.replace(/,/g, '.');
|
||||
}
|
||||
|
||||
return cleanedPrice;
|
||||
}
|
||||
|
||||
// format the price according to user prefs
|
||||
function formatPrice(repl) {
|
||||
// set rounding
|
||||
var formattedPrice = (preferences.round) ? repl.toFixed(0) : repl.toFixed(2);
|
||||
|
||||
// set decimal separator
|
||||
if (preferences.sepDec !== '.') formattedPrice = formattedPrice.replace('.', preferences.sepDec);
|
||||
|
||||
// set thousand separator
|
||||
if (preferences.sepTho !== '') {
|
||||
for (var i = ((preferences.round) ? formattedPrice.length : formattedPrice.indexOf(preferences.sepDec)) - 3; i > 0; i -= 3) {
|
||||
formattedPrice = formattedPrice.slice(0, i) + preferences.sepTho + formattedPrice.slice(i);
|
||||
}
|
||||
}
|
||||
|
||||
// add symbol
|
||||
if (preferences.symbPos === 'a') {
|
||||
formattedPrice = formattedPrice + ((preferences.symbSep) ? ' ' : '') + preferences.symbol;
|
||||
} else {
|
||||
formattedPrice = preferences.symbol + ((preferences.symbSep) ? ' ' : '') + formattedPrice;
|
||||
}
|
||||
|
||||
return formattedPrice;
|
||||
}
|
||||
|
||||
|
||||
// replace the prices in the text node with the converted data nodes
|
||||
function replaceText(node, dataNodes) {
|
||||
var parentNode = node.parentNode;
|
||||
var tmpDivElem = document.createElement('div');
|
||||
tmpDivElem.appendChild(node.cloneNode());
|
||||
|
||||
dataNodes.forEach(function eachDataNode(dataNode) {
|
||||
var replTxt = dataNode.title;
|
||||
var replaced = false;
|
||||
|
||||
tmpDivElem.childNodes.forEach(function eachChildNode(childNode) {
|
||||
if (replaced || childNode.nodeType !== 3) return;
|
||||
|
||||
var nodeTxt = childNode.nodeValue;
|
||||
var matchInd = nodeTxt.indexOf(replTxt);
|
||||
|
||||
if (matchInd === -1) return;
|
||||
|
||||
var tmpTxt;
|
||||
var tmpTxtNode;
|
||||
var replDivElem = document.createElement('div');
|
||||
|
||||
if (matchInd > 0) {
|
||||
tmpTxt = nodeTxt.slice(0, matchInd);
|
||||
tmpTxtNode = document.createTextNode(tmpTxt);
|
||||
replDivElem.appendChild(tmpTxtNode);
|
||||
}
|
||||
|
||||
replDivElem.appendChild(dataNode);
|
||||
|
||||
if (matchInd + replTxt.length < nodeTxt.length) {
|
||||
tmpTxt = nodeTxt.slice(matchInd + replTxt.length);
|
||||
tmpTxtNode = document.createTextNode(tmpTxt);
|
||||
replDivElem.appendChild(tmpTxtNode);
|
||||
}
|
||||
|
||||
while (replDivElem.firstChild) {
|
||||
tmpDivElem.insertBefore(replDivElem.firstChild, childNode);
|
||||
}
|
||||
tmpDivElem.removeChild(childNode);
|
||||
|
||||
replaced = true;
|
||||
});
|
||||
});
|
||||
|
||||
while (tmpDivElem.firstChild) {
|
||||
parentNode.insertBefore(tmpDivElem.firstChild, node);
|
||||
}
|
||||
parentNode.removeChild(node);
|
||||
}
|
||||
}).apply(window.SCSCC);
|
||||
|
||||
window.SCSCC.init();
|
BIN
icons/icon16.png
Normal file
BIN
icons/icon16.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 593 B |
BIN
icons/icon16_off.png
Normal file
BIN
icons/icon16_off.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 483 B |
BIN
icons/icon32.png
Normal file
BIN
icons/icon32.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.4 KiB |
BIN
icons/icon32_off.png
Normal file
BIN
icons/icon32_off.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.1 KiB |
BIN
icons/icon48.png
Normal file
BIN
icons/icon48.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.5 KiB |
BIN
icons/icon48_off.png
Normal file
BIN
icons/icon48_off.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.8 KiB |
514
manifest.json
Normal file
514
manifest.json
Normal file
@ -0,0 +1,514 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "SCs Currency Converter",
|
||||
"description": "Convert US Dollar, British Pound Sterling and Euro prices to a user set currency",
|
||||
"version": "0.9.0",
|
||||
|
||||
"icons": {
|
||||
"48": "icons/icon48.png"
|
||||
},
|
||||
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "scscurrencyconverter@sarics",
|
||||
"strict_min_version": "45.0"
|
||||
}
|
||||
},
|
||||
|
||||
"permissions": [
|
||||
"https://*/*",
|
||||
"storage",
|
||||
"notifications"
|
||||
],
|
||||
|
||||
"background": {
|
||||
"scripts": ["background.js"]
|
||||
},
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["content_scripts/scscc2.js"]
|
||||
}
|
||||
],
|
||||
|
||||
"browser_action": {
|
||||
"browser_style": true,
|
||||
"default_icon": {
|
||||
"16": "icons/icon16.png",
|
||||
"32": "icons/icon32.png",
|
||||
"48": "icons/icon48.png"
|
||||
},
|
||||
"default_title": "SCs Currency Converter",
|
||||
"default_popup": "popup/popup.html"
|
||||
},
|
||||
|
||||
"options_ui": {
|
||||
"page": "options/options.html"
|
||||
},
|
||||
|
||||
"preferences": [
|
||||
{
|
||||
"title": "Convert to",
|
||||
"name": "toCurr",
|
||||
"type": "menulist",
|
||||
"value": "",
|
||||
"options": [
|
||||
{
|
||||
"value": "",
|
||||
"label": "Please select a currency"
|
||||
},
|
||||
{
|
||||
"value": "DZD",
|
||||
"label": "Algerian Dinar"
|
||||
},
|
||||
{
|
||||
"value": "ARS",
|
||||
"label": "Argentine Peso"
|
||||
},
|
||||
{
|
||||
"value": "AUD",
|
||||
"label": "Australian Dollar"
|
||||
},
|
||||
{
|
||||
"value": "BHD",
|
||||
"label": "Bahraini Dinar"
|
||||
},
|
||||
{
|
||||
"value": "BOB",
|
||||
"label": "Bolivian Boliviano"
|
||||
},
|
||||
{
|
||||
"value": "BWP",
|
||||
"label": "Botswanan Pula"
|
||||
},
|
||||
{
|
||||
"value": "BRL",
|
||||
"label": "Brazilian Real"
|
||||
},
|
||||
{
|
||||
"value": "GBP",
|
||||
"label": "British Pound Sterling"
|
||||
},
|
||||
{
|
||||
"value": "BND",
|
||||
"label": "Brunei Dollar"
|
||||
},
|
||||
{
|
||||
"value": "BGN",
|
||||
"label": "Bulgarian Lev"
|
||||
},
|
||||
{
|
||||
"value": "CAD",
|
||||
"label": "Canadian Dollar"
|
||||
},
|
||||
{
|
||||
"value": "KYD",
|
||||
"label": "Cayman Islands Dollar"
|
||||
},
|
||||
{
|
||||
"value": "XOF",
|
||||
"label": "CFA Franc BCEAO"
|
||||
},
|
||||
{
|
||||
"value": "CLP",
|
||||
"label": "Chilean Peso"
|
||||
},
|
||||
{
|
||||
"value": "CNY",
|
||||
"label": "Chinese Yuan"
|
||||
},
|
||||
{
|
||||
"value": "COP",
|
||||
"label": "Colombian Peso"
|
||||
},
|
||||
{
|
||||
"value": "CRC",
|
||||
"label": "Costa Rican Colón"
|
||||
},
|
||||
{
|
||||
"value": "HRK",
|
||||
"label": "Croatian Kuna"
|
||||
},
|
||||
{
|
||||
"value": "CZK",
|
||||
"label": "Czech Republic Koruna"
|
||||
},
|
||||
{
|
||||
"value": "DKK",
|
||||
"label": "Danish Krone"
|
||||
},
|
||||
{
|
||||
"value": "DOP",
|
||||
"label": "Dominican Peso"
|
||||
},
|
||||
{
|
||||
"value": "EGP",
|
||||
"label": "Egyptian Pound"
|
||||
},
|
||||
{
|
||||
"value": "EEK",
|
||||
"label": "Estonian Kroon"
|
||||
},
|
||||
{
|
||||
"value": "EUR",
|
||||
"label": "Euro"
|
||||
},
|
||||
{
|
||||
"value": "FJD",
|
||||
"label": "Fijian Dollar"
|
||||
},
|
||||
{
|
||||
"value": "HNL",
|
||||
"label": "Honduran Lempira"
|
||||
},
|
||||
{
|
||||
"value": "HKD",
|
||||
"label": "Hong Kong Dollar"
|
||||
},
|
||||
{
|
||||
"value": "HUF",
|
||||
"label": "Hungarian Forint"
|
||||
},
|
||||
{
|
||||
"value": "INR",
|
||||
"label": "Indian Rupee"
|
||||
},
|
||||
{
|
||||
"value": "IDR",
|
||||
"label": "Indonesian Rupiah"
|
||||
},
|
||||
{
|
||||
"value": "ILS",
|
||||
"label": "Israeli New Sheqel"
|
||||
},
|
||||
{
|
||||
"value": "JMD",
|
||||
"label": "Jamaican Dollar"
|
||||
},
|
||||
{
|
||||
"value": "JPY",
|
||||
"label": "Japanese Yen"
|
||||
},
|
||||
{
|
||||
"value": "JOD",
|
||||
"label": "Jordanian Dinar"
|
||||
},
|
||||
{
|
||||
"value": "KZT",
|
||||
"label": "Kazakhstani Tenge"
|
||||
},
|
||||
{
|
||||
"value": "KES",
|
||||
"label": "Kenyan Shilling"
|
||||
},
|
||||
{
|
||||
"value": "KWD",
|
||||
"label": "Kuwaiti Dinar"
|
||||
},
|
||||
{
|
||||
"value": "LVL",
|
||||
"label": "Latvian Lats"
|
||||
},
|
||||
{
|
||||
"value": "LBP",
|
||||
"label": "Lebanese Pound"
|
||||
},
|
||||
{
|
||||
"value": "LTL",
|
||||
"label": "Lithuanian Litas"
|
||||
},
|
||||
{
|
||||
"value": "MKD",
|
||||
"label": "Macedonian Denar"
|
||||
},
|
||||
{
|
||||
"value": "MYR",
|
||||
"label": "Malaysian Ringgit"
|
||||
},
|
||||
{
|
||||
"value": "MUR",
|
||||
"label": "Mauritian Rupee"
|
||||
},
|
||||
{
|
||||
"value": "MXN",
|
||||
"label": "Mexican Peso"
|
||||
},
|
||||
{
|
||||
"value": "MDL",
|
||||
"label": "Moldovan Leu"
|
||||
},
|
||||
{
|
||||
"value": "MAD",
|
||||
"label": "Moroccan Dirham"
|
||||
},
|
||||
{
|
||||
"value": "NAD",
|
||||
"label": "Namibian Dollar"
|
||||
},
|
||||
{
|
||||
"value": "NPR",
|
||||
"label": "Nepalese Rupee"
|
||||
},
|
||||
{
|
||||
"value": "ANG",
|
||||
"label": "Netherlands Antillean Guilder"
|
||||
},
|
||||
{
|
||||
"value": "TWD",
|
||||
"label": "New Taiwan Dollar"
|
||||
},
|
||||
{
|
||||
"value": "NZD",
|
||||
"label": "New Zealand Dollar"
|
||||
},
|
||||
{
|
||||
"value": "NIO",
|
||||
"label": "Nicaraguan Córdoba"
|
||||
},
|
||||
{
|
||||
"value": "NGN",
|
||||
"label": "Nigerian Naira"
|
||||
},
|
||||
{
|
||||
"value": "NOK",
|
||||
"label": "Norwegian Krone"
|
||||
},
|
||||
{
|
||||
"value": "OMR",
|
||||
"label": "Omani Rial"
|
||||
},
|
||||
{
|
||||
"value": "PKR",
|
||||
"label": "Pakistani Rupee"
|
||||
},
|
||||
{
|
||||
"value": "PGK",
|
||||
"label": "Papua New Guinean Kina"
|
||||
},
|
||||
{
|
||||
"value": "PYG",
|
||||
"label": "Paraguayan Guarani"
|
||||
},
|
||||
{
|
||||
"value": "PEN",
|
||||
"label": "Peruvian Nuevo Sol"
|
||||
},
|
||||
{
|
||||
"value": "PHP",
|
||||
"label": "Philippine Peso"
|
||||
},
|
||||
{
|
||||
"value": "PLN",
|
||||
"label": "Polish Zloty"
|
||||
},
|
||||
{
|
||||
"value": "QAR",
|
||||
"label": "Qatari Rial"
|
||||
},
|
||||
{
|
||||
"value": "RON",
|
||||
"label": "Romanian Leu"
|
||||
},
|
||||
{
|
||||
"value": "RUB",
|
||||
"label": "Russian Ruble"
|
||||
},
|
||||
{
|
||||
"value": "SVC",
|
||||
"label": "Salvadoran Colón"
|
||||
},
|
||||
{
|
||||
"value": "SAR",
|
||||
"label": "Saudi Riyal"
|
||||
},
|
||||
{
|
||||
"value": "RSD",
|
||||
"label": "Serbian Dinar"
|
||||
},
|
||||
{
|
||||
"value": "SCR",
|
||||
"label": "Seychellois Rupee"
|
||||
},
|
||||
{
|
||||
"value": "SLL",
|
||||
"label": "Sierra Leonean Leone"
|
||||
},
|
||||
{
|
||||
"value": "SGD",
|
||||
"label": "Singapore Dollar"
|
||||
},
|
||||
{
|
||||
"value": "SKK",
|
||||
"label": "Slovak Koruna"
|
||||
},
|
||||
{
|
||||
"value": "ZAR",
|
||||
"label": "South African Rand"
|
||||
},
|
||||
{
|
||||
"value": "KRW",
|
||||
"label": "South Korean Won"
|
||||
},
|
||||
{
|
||||
"value": "LKR",
|
||||
"label": "Sri Lankan Rupee"
|
||||
},
|
||||
{
|
||||
"value": "SEK",
|
||||
"label": "Swedish Krona"
|
||||
},
|
||||
{
|
||||
"value": "CHF",
|
||||
"label": "Swiss Franc"
|
||||
},
|
||||
{
|
||||
"value": "TZS",
|
||||
"label": "Tanzanian Shilling"
|
||||
},
|
||||
{
|
||||
"value": "THB",
|
||||
"label": "Thai Baht"
|
||||
},
|
||||
{
|
||||
"value": "TTD",
|
||||
"label": "Trinidad and Tobago Dollar"
|
||||
},
|
||||
{
|
||||
"value": "TND",
|
||||
"label": "Tunisian Dinar"
|
||||
},
|
||||
{
|
||||
"value": "TRY",
|
||||
"label": "Turkish Lira"
|
||||
},
|
||||
{
|
||||
"value": "UGX",
|
||||
"label": "Ugandan Shilling"
|
||||
},
|
||||
{
|
||||
"value": "UAH",
|
||||
"label": "Ukrainian Hryvnia"
|
||||
},
|
||||
{
|
||||
"value": "AED",
|
||||
"label": "United Arab Emirates Dirham"
|
||||
},
|
||||
{
|
||||
"value": "UYU",
|
||||
"label": "Uruguayan Peso"
|
||||
},
|
||||
{
|
||||
"value": "USD",
|
||||
"label": "US Dollar"
|
||||
},
|
||||
{
|
||||
"value": "UZS",
|
||||
"label": "Uzbekistan Som"
|
||||
},
|
||||
{
|
||||
"value": "VEF",
|
||||
"label": "Venezuelan Bolívar"
|
||||
},
|
||||
{
|
||||
"value": "VND",
|
||||
"label": "Vietnamese Dong"
|
||||
},
|
||||
{
|
||||
"value": "YER",
|
||||
"label": "Yemeni Rial"
|
||||
},
|
||||
{
|
||||
"value": "ZMK",
|
||||
"label": "Zambian Kwacha (1968–2012)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Round price",
|
||||
"name": "round",
|
||||
"type": "bool",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"title": "Currency symbol",
|
||||
"name": "symbol",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"title": "Symbol position",
|
||||
"name": "symbPos",
|
||||
"type": "radio",
|
||||
"value": "a",
|
||||
"options": [
|
||||
{
|
||||
"value": "b",
|
||||
"label": "before"
|
||||
},
|
||||
{
|
||||
"value": "a",
|
||||
"label": "after"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Separate symbol from price",
|
||||
"name": "symbSep",
|
||||
"type": "bool",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"title": "Thousand separator",
|
||||
"name": "sepTho",
|
||||
"type": "menulist",
|
||||
"value": " ",
|
||||
"options": [
|
||||
{
|
||||
"value": "",
|
||||
"label": "none"
|
||||
},
|
||||
{
|
||||
"value": " ",
|
||||
"label": "space"
|
||||
},
|
||||
{
|
||||
"value": ",",
|
||||
"label": ","
|
||||
},
|
||||
{
|
||||
"value": ".",
|
||||
"label": "."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Decimal separator",
|
||||
"name": "sepDec",
|
||||
"type": "menulist",
|
||||
"value": ",",
|
||||
"options": [
|
||||
{
|
||||
"value": ",",
|
||||
"label": ","
|
||||
},
|
||||
{
|
||||
"value": ".",
|
||||
"label": "."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Add custom style to converted prices",
|
||||
"name": "style",
|
||||
"type": "bool",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"title": "Show exchange rate update notifications",
|
||||
"name": "noti",
|
||||
"type": "bool",
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
}
|
21
options/options.css
Normal file
21
options/options.css
Normal file
@ -0,0 +1,21 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #333333;
|
||||
font-family: sans-serif;
|
||||
font-size: 12pt;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#options {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
#options > tr > td {
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid #c1c1c1;
|
||||
}
|
||||
#options > tr:last-child > td {
|
||||
border-bottom: none;
|
||||
}
|
12
options/options.html
Normal file
12
options/options.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>SCsCC - Options</title>
|
||||
<link rel="stylesheet" type="text/css" href="options.css">
|
||||
</head>
|
||||
<body>
|
||||
<table id="options"></table>
|
||||
<script type="text/javascript" src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
153
options/options.js
Normal file
153
options/options.js
Normal file
@ -0,0 +1,153 @@
|
||||
var manifest = chrome.runtime.getManifest();
|
||||
var preferences = manifest.preferences;
|
||||
var storagePrefs;
|
||||
var bodyElem = document.body;
|
||||
|
||||
chrome.storage.local.get('preferences', function callback(storage) {
|
||||
storagePrefs = storage.preferences;
|
||||
|
||||
buildOptionsForm();
|
||||
|
||||
chrome.storage.onChanged.addListener(function listener(changes) {
|
||||
if (changes.preferences) {
|
||||
var changedPrefs = {};
|
||||
|
||||
Object.keys(changes.preferences.newValue).forEach(function cb(prefKey) {
|
||||
var oldValue = storagePrefs[prefKey];
|
||||
var newValue = changes.preferences.newValue[prefKey];
|
||||
if (newValue !== oldValue) changedPrefs[prefKey] = newValue;
|
||||
});
|
||||
|
||||
storagePrefs = changes.preferences.newValue;
|
||||
if (Object.keys(changedPrefs).length) refreshOptionsForm(changedPrefs);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function buildOptionsForm() {
|
||||
var tableElem = document.getElementById('options');
|
||||
|
||||
preferences.forEach(function callback(pref) {
|
||||
var value = storagePrefs[pref.name];
|
||||
|
||||
var trElem = document.createElement('tr');
|
||||
|
||||
var labelTdElem = document.createElement('td');
|
||||
var labelElem = document.createElement('label');
|
||||
labelElem.htmlFor = 'pref-' + pref.name;
|
||||
labelElem.textContent = pref.title;
|
||||
|
||||
labelTdElem.appendChild(labelElem);
|
||||
trElem.appendChild(labelTdElem);
|
||||
|
||||
var formControlTdElem = document.createElement('td');
|
||||
var formControlElem;
|
||||
if (pref.type === 'string') {
|
||||
formControlElem = document.createElement('input');
|
||||
formControlElem.type = 'text';
|
||||
formControlElem.name = pref.name;
|
||||
formControlElem.value = value || '';
|
||||
|
||||
formControlElem.addEventListener('input', onChange);
|
||||
} else if (pref.type === 'bool') {
|
||||
formControlElem = document.createElement('input');
|
||||
formControlElem.type = 'checkbox';
|
||||
formControlElem.name = pref.name;
|
||||
if (value === true) formControlElem.checked = true;
|
||||
|
||||
formControlElem.addEventListener('change', onChange);
|
||||
} else if (pref.type === 'radio') {
|
||||
formControlElem = document.createElement('div');
|
||||
|
||||
pref.options.forEach(function cb(opt) {
|
||||
var optLabelElem = document.createElement('label');
|
||||
|
||||
var radioElem = document.createElement('input');
|
||||
radioElem.type = 'radio';
|
||||
radioElem.name = pref.name;
|
||||
radioElem.value = opt.value;
|
||||
if (value === opt.value) radioElem.setAttribute('checked', '');
|
||||
|
||||
radioElem.addEventListener('change', onChange);
|
||||
|
||||
optLabelElem.appendChild(radioElem);
|
||||
optLabelElem.appendChild(document.createTextNode(opt.label));
|
||||
formControlElem.appendChild(optLabelElem);
|
||||
});
|
||||
} else if (pref.type === 'menulist') {
|
||||
formControlElem = document.createElement('select');
|
||||
formControlElem.name = pref.name;
|
||||
|
||||
pref.options.forEach(function cb(opt) {
|
||||
var optionElem = document.createElement('option');
|
||||
optionElem.value = opt.value;
|
||||
optionElem.textContent = opt.label;
|
||||
if (value === opt.value) optionElem.setAttribute('selected', '');
|
||||
|
||||
formControlElem.appendChild(optionElem);
|
||||
});
|
||||
|
||||
formControlElem.addEventListener('change', onChange);
|
||||
}
|
||||
|
||||
if (formControlElem) {
|
||||
formControlElem.id = 'pref-' + pref.name;
|
||||
formControlElem.className = 'form-control';
|
||||
|
||||
formControlTdElem.appendChild(formControlElem);
|
||||
trElem.appendChild(formControlTdElem);
|
||||
}
|
||||
|
||||
tableElem.appendChild(trElem);
|
||||
});
|
||||
|
||||
bodyElem.appendChild(tableElem);
|
||||
}
|
||||
|
||||
function refreshOptionsForm(changedPrefs) {
|
||||
Object.keys(changedPrefs).forEach(function callback(prefName) {
|
||||
var formControlElem = document.querySelector('[name="' + prefName + '"]');
|
||||
|
||||
if (formControlElem) {
|
||||
if (formControlElem.type === 'radio') {
|
||||
var radioElem = document.querySelector('[name="' + prefName + '"][value="' + changedPrefs[prefName] + '"]');
|
||||
if (radioElem) radioElem.checked = true;
|
||||
} else if (formControlElem.type === 'checkbox') {
|
||||
formControlElem.checked = changedPrefs[prefName];
|
||||
} else {
|
||||
formControlElem.value = changedPrefs[prefName];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onChange(event) {
|
||||
var target = event.target;
|
||||
var prefName = target.name;
|
||||
var prefValue = target.type === 'checkbox' ? target.checked : target.value;
|
||||
|
||||
var changedPrefs = {};
|
||||
changedPrefs[prefName] = prefValue;
|
||||
|
||||
// change the symbol on currency change
|
||||
if (prefName === 'toCurr') {
|
||||
changedPrefs.symbol = prefValue;
|
||||
}
|
||||
|
||||
// if a space inserted before the symbol, remove it, and set symbSep to true
|
||||
if (prefName === 'symbol' && (prefValue.charAt(0) === ' ' || prefValue.charAt(prefValue.length - 1) === ' ')) {
|
||||
if (!storagePrefs.symbSep) changedPrefs.symbSep = true;
|
||||
|
||||
target.value = prefValue.trim();
|
||||
delete changedPrefs.symbol;
|
||||
}
|
||||
|
||||
// don't var to set the same thousand and decimal separator
|
||||
if (prefName === 'sepTho' && prefValue === storagePrefs.sepDec) {
|
||||
changedPrefs.sepDec = prefValue === ',' ? '.' : ',';
|
||||
} else if (prefName === 'sepDec' && prefValue === storagePrefs.sepTho) {
|
||||
changedPrefs.sepTho = prefValue === ',' ? '.' : ',';
|
||||
}
|
||||
|
||||
if (Object.keys(changedPrefs).length) chrome.storage.local.set({ preferences: Object.assign({}, storagePrefs, changedPrefs) });
|
||||
}
|
14
package.json
Normal file
14
package.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "scscc",
|
||||
"description": "Convert US Dollar, British Pound Sterling and Euro prices to a user set currency",
|
||||
"version": "0.9.0",
|
||||
"author": "sarics",
|
||||
"homepage": "https://addons.mozilla.org/addon/scscurrencyconverter/",
|
||||
"license": "GPL-2.0",
|
||||
"main": "index.js",
|
||||
"devDependencies": {
|
||||
"eslint": "^3.12.2",
|
||||
"eslint-config-airbnb-base": "^11.0.0",
|
||||
"eslint-plugin-import": "^2.2.0"
|
||||
}
|
||||
}
|
39
popup/popup.css
Normal file
39
popup/popup.css
Normal file
@ -0,0 +1,39 @@
|
||||
body {
|
||||
width: 250px;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
font-family: sans-serif;
|
||||
font-size: 12pt;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
height: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.4em 0 0.4em 2px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0 5px 0 15px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 0 0.4em 0;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
span.curr {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
span.upd {
|
||||
padding-left: 10px;
|
||||
font-size: 0.7em;
|
||||
}
|
18
popup/popup.html
Normal file
18
popup/popup.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>SCsCC - Popup</title>
|
||||
<link rel="stylesheet" type="text/css" href="popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<button id="state"></button><br>
|
||||
<button id="options">Options</button>
|
||||
<p id="toCurr"></p>
|
||||
<p><strong>Exchange rates:</strong></p>
|
||||
<ul id="rates"></ul>
|
||||
<button id="reset">Reset exchange rates</button>
|
||||
|
||||
<script type="text/javascript" src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
147
popup/popup.js
Normal file
147
popup/popup.js
Normal file
@ -0,0 +1,147 @@
|
||||
var manifest = chrome.runtime.getManifest();
|
||||
var toCurrPref = manifest.preferences.filter(function filterPref(pref) {
|
||||
return pref.name === 'toCurr';
|
||||
})[0] || {};
|
||||
var toCurrOpts = toCurrPref.options || [];
|
||||
|
||||
var resBtnElem = document.querySelector('#reset');
|
||||
var stateBtnElem = document.querySelector('#state');
|
||||
var optionsBtnElem = document.querySelector('#options');
|
||||
var toCurrPElem = document.querySelector('#toCurr');
|
||||
var ratesUlElem = document.querySelector('#rates');
|
||||
|
||||
stateBtnElem.addEventListener('click', function listener() {
|
||||
var onGet = function onGet(storage) {
|
||||
var preferences = storage.preferences;
|
||||
preferences.enabled = !preferences.enabled;
|
||||
|
||||
chrome.storage.local.set({ preferences: preferences });
|
||||
};
|
||||
|
||||
chrome.storage.local.get('preferences', onGet);
|
||||
});
|
||||
|
||||
resBtnElem.addEventListener('click', function listener() {
|
||||
chrome.storage.local.set({ currRates: {} });
|
||||
});
|
||||
|
||||
optionsBtnElem.addEventListener('click', function listener() {
|
||||
chrome.runtime.openOptionsPage();
|
||||
});
|
||||
|
||||
|
||||
chrome.storage.local.get(null, function callback(storage) {
|
||||
var enabled = storage.preferences.enabled;
|
||||
var toCurr = storage.preferences.toCurr;
|
||||
var currRates = storage.currRates || {};
|
||||
|
||||
setState(enabled);
|
||||
setToCurr(toCurr);
|
||||
refreshCurrRatesList(currRates);
|
||||
|
||||
chrome.storage.onChanged.addListener(onStorageChange);
|
||||
});
|
||||
|
||||
function onStorageChange(changes) {
|
||||
if (changes.preferences) {
|
||||
var oldPrefs = changes.preferences.oldValue;
|
||||
var newPrefs = changes.preferences.newValue;
|
||||
|
||||
if (oldPrefs.enabled !== newPrefs.enabled) setState(newPrefs.enabled);
|
||||
if (oldPrefs.toCurr !== newPrefs.toCurr) setToCurr(newPrefs.toCurr);
|
||||
}
|
||||
|
||||
if (changes.currRates) {
|
||||
refreshCurrRatesList(changes.currRates.newValue);
|
||||
}
|
||||
}
|
||||
|
||||
function setState(enabled) {
|
||||
stateBtnElem.textContent = enabled ? 'Turn off' : 'Turn on';
|
||||
}
|
||||
|
||||
function setToCurr(curr) {
|
||||
while (toCurrPElem.firstChild) {
|
||||
toCurrPElem.removeChild(toCurrPElem.firstChild);
|
||||
}
|
||||
|
||||
var strongElem = document.createElement('strong');
|
||||
var brElem = document.createElement('br');
|
||||
var textElem = document.createTextNode('');
|
||||
|
||||
if (curr) {
|
||||
var toCurr = toCurrOpts.filter(function filterToCurrOpt(toCurrOpt) {
|
||||
return toCurrOpt.value === curr;
|
||||
})[0] || null;
|
||||
|
||||
strongElem.textContent = 'Convert to:';
|
||||
textElem.textContent = toCurr ? toCurr.label + ' (' + toCurr.value + ')' : curr;
|
||||
} else {
|
||||
strongElem.textContent = 'No set currency!';
|
||||
textElem.textContent = 'Please select a currency on the options page.';
|
||||
}
|
||||
|
||||
toCurrPElem.appendChild(strongElem);
|
||||
toCurrPElem.appendChild(brElem);
|
||||
toCurrPElem.appendChild(textElem);
|
||||
}
|
||||
|
||||
function refreshCurrRatesList(currRates) {
|
||||
while (ratesUlElem.firstChild) {
|
||||
ratesUlElem.removeChild(ratesUlElem.firstChild);
|
||||
}
|
||||
|
||||
Object.keys(currRates).forEach(function callback(key) {
|
||||
var currs = key.split('to');
|
||||
var liElem = document.createElement('li');
|
||||
|
||||
// span.curr - fromCurr to toCurr:
|
||||
var currSpanElem = document.createElement('span');
|
||||
currSpanElem.className = 'curr';
|
||||
currSpanElem.textContent = currs[0] + ' to ' + currs[1] + ': ';
|
||||
liElem.appendChild(currSpanElem);
|
||||
|
||||
// strong - currRate
|
||||
var rateStrongElem = document.createElement('strong');
|
||||
rateStrongElem.textContent = currRates[key].value;
|
||||
liElem.appendChild(rateStrongElem);
|
||||
|
||||
liElem.appendChild(document.createElement('br'));
|
||||
|
||||
// span.upd - lastUpdate
|
||||
var updSpanElem = document.createElement('span');
|
||||
updSpanElem.className = 'upd';
|
||||
updSpanElem.textContent = lastUpdate(currRates[key].updatedAt);
|
||||
liElem.appendChild(updSpanElem);
|
||||
|
||||
ratesUlElem.appendChild(liElem);
|
||||
});
|
||||
|
||||
if (ratesUlElem.childNodes.length === 0) {
|
||||
resBtnElem.style.display = 'none';
|
||||
|
||||
var liElem = document.createElement('li');
|
||||
liElem.textContent = 'No downloaded exchange rate yet.';
|
||||
|
||||
ratesUlElem.appendChild(liElem);
|
||||
} else {
|
||||
resBtnElem.style.display = 'initial';
|
||||
}
|
||||
}
|
||||
|
||||
function lastUpdate(currRateLT) {
|
||||
var lt = Date.now() - currRateLT;
|
||||
|
||||
if (lt > 3600000) {
|
||||
lt = Math.floor(lt / 3600000);
|
||||
if (lt === 1) lt = 'more than ' + lt + ' hour';
|
||||
else lt = 'more than ' + lt + ' hours';
|
||||
} else {
|
||||
lt = Math.floor(lt / 60000);
|
||||
if (lt === 0) lt = 'less than a minute';
|
||||
else if (lt === 1) lt += ' minute';
|
||||
else lt += ' minutes';
|
||||
}
|
||||
|
||||
return '(updated ' + lt + ' ago)';
|
||||
}
|
Loading…
Reference in New Issue
Block a user