diff --git a/assets/css/style.css b/assets/css/style.css deleted file mode 100644 index d52c5e7..0000000 --- a/assets/css/style.css +++ /dev/null @@ -1,2 +0,0 @@ -/*Currently Still Work with UI Design*/ -* {user-select: none;} \ No newline at end of file diff --git a/assets/img/AM.png b/assets/img/AM.png deleted file mode 100644 index 3b1ef6e..0000000 Binary files a/assets/img/AM.png and /dev/null differ diff --git a/assets/img/Menu.jpg b/assets/img/Menu.jpg index 71a4565..af11c2d 100644 Binary files a/assets/img/Menu.jpg and b/assets/img/Menu.jpg differ diff --git a/assets/js/app.js b/assets/js/app.js index e459d36..f0aa6cf 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,80 +1,351 @@ -//Chrome extension, i only work on it when I have free time 😊 -//Feel free to create pull request -//made by @idevmans aka AyManXd 2019-2022 - -console.log('CurrencyConverter by AyManXd Injected'); - -//async function apiFetch () { -// var api = await fetch('https://api.ratesapi.io/api/latest?symbols=RUB&base=USD'); -// return api.rates.RUB -//} -//console.log(apiFetch()); - -// let currencyApi = 75 //hardcoded for a while - -//store it somewhere else -const websitesList = [ - { "domain": 'https://swap.gg', "class": "div.item span.p", "regex": /\$+|,/g }, -]; - -let currentWebSite = websitesList.filter(websiteElement => { - if (websiteElement.domain == document.location.origin) { - return websiteElement; - } -}); - -let selector = currentWebSite[0].class.toString(); - -// function starParse() { -// var classSelector = currentSite[0].class; -// var notParsedItems = document.querySelectorAll(classSelector + ":not(.amr-parsed-item)"); -// var parsedItem = document.querySelectorAll(classSelector + '.amr-parsed-item'); -// if (notParsedItems) { -// nodeListToArray(notParsedItems).forEach(el => { -// el.innerText = objectProcessing(el); -// el.classList.add("amr-parsed-item"); -// }); -// } else { -// nodeListToArray(parsedItem).forEach(el => { -// objectProcessing(el); -// el.classList.add("amr-parsed-item"); -// }); -// } -// } - -// //transform node to HTML element -// function nodeListToArray(nodeToArr) { -// return Array.prototype.slice.call(nodeToArr); -// } - -// function objectProcessing(foreachItem) { -// let produce = foreachItem.innerText.replace(/\$+|,/g, '') * currencyApi; -// return produce.toFixed(2); -// } - -// if (currentSite) { -// setInterval(() => starParse(), 3000); -// } - - -let observer = new IntersectionObserver((entries, observer) => { - console.log(entries,observer); - entries.forEach(entry => { - console.log(entry); - - if (entry.isIntersecting) { - console.log(entry); +console.warn('CurrencyConverter by @fosterushka 2019-2025'); + +class DOMCurrencyConverter { + constructor() { + this.rates = null; + this.fromCurrency = 'USD'; + this.toCurrency = 'RUB'; + this.amount = 1; + this.convertedElements = new Map(); + this.selectionMode = false; + this.highlightOverlay = null; + + this.handleMouseMove = this.handleMouseMove.bind(this); + this.handleClick = this.handleClick.bind(this); + this.handleCurrencyChange = this.handleCurrencyChange.bind(this); + this.handleSwitchCurrencies = this.handleSwitchCurrencies.bind(this); + + this.initialize(); + } + + async initialize() { + try { + await this.loadPreferences(); + await this.loadRates(); + this.setupMessageListener(); + this.setupMutationObserver(); + this.createHighlightOverlay(); + + if (chrome.runtime?.id) { + this.initializePopup(); + } + + console.log('DOMCurrencyConverter initialized with:', { + fromCurrency: this.fromCurrency, + toCurrency: this.toCurrency + }); + } catch (error) { + console.error('Initialization error:', error); + } + } + + initializePopup() { + this.fromBox = document.getElementById('fromCurrency'); + this.toBox = document.getElementById('toCurrency'); + this.switchButton = document.querySelector('.switch-button'); + this.dropdown = document.getElementById('currencyDropdown'); + this.rateInfo = document.querySelector('.rate-info'); + this.selectModeButton = document.getElementById('selectModeButton'); + + this.fromBox?.addEventListener('click', () => this.showDropdown('from')); + this.toBox?.addEventListener('click', () => this.showDropdown('to')); + this.switchButton?.addEventListener('click', this.handleSwitchCurrencies); + this.selectModeButton?.addEventListener('click', () => this.toggleSelectionMode()); + + document.addEventListener('click', (e) => { + if (this.dropdown && !this.dropdown.contains(e.target) && + !this.fromBox.contains(e.target) && + !this.toBox.contains(e.target)) { + this.dropdown.style.display = 'none'; + } + }); + + this.updatePopupUI(); + } + + async loadPreferences() { + const prefs = await chrome.storage.local.get(['fromCurrency', 'toCurrency']); + if (prefs.fromCurrency) this.fromCurrency = prefs.fromCurrency; + if (prefs.toCurrency) this.toCurrency = prefs.toCurrency; + } + + async loadRates() { + try { + const stored = localStorage.getItem('currencyRates'); + const storedTime = localStorage.getItem('lastFetchTime'); + + if (stored && storedTime) { + const timeDiff = Date.now() - parseInt(storedTime); + if (timeDiff < 24 * 60 * 60 * 1000) { + this.rates = JSON.parse(stored); + this.updatePopupUI(); + return; + } + } + + const response = await fetch('https://api.exchangerate-api.com/v4/latest/USD'); + const data = await response.json(); + this.rates = data.rates; + localStorage.setItem('currencyRates', JSON.stringify(this.rates)); + localStorage.setItem('lastFetchTime', Date.now().toString()); + this.updatePopupUI(); + } catch (error) { + console.error('Error fetching rates:', error); + const stored = localStorage.getItem('currencyRates'); + if (stored) { + this.rates = JSON.parse(stored); + this.updatePopupUI(); + } + } + } + + setupMessageListener() { + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.action === 'toggleSelection') { + this.toggleSelectionMode(message.selectionMode); + sendResponse({ success: true }); + } + return true; + }); + } + + createHighlightOverlay() { + if (document.body) { + this.highlightOverlay = document.createElement('div'); + this.highlightOverlay.style.cssText = ` + position: fixed; + pointer-events: none; + z-index: 10000; + border: 2px solid #4a9eff; + border-radius: 4px; + background: rgba(74, 158, 255, 0.1); + display: none; + `; + document.body.appendChild(this.highlightOverlay); + } + } + + toggleSelectionMode(enabled = !this.selectionMode) { + this.selectionMode = enabled; + + if (document.body) { + document.body.style.cursor = enabled ? 'crosshair' : ''; + } + + if (enabled) { + document.addEventListener('mousemove', this.handleMouseMove); + document.addEventListener('click', this.handleClick); } else { - a.classList.remove('active'); + if (this.highlightOverlay) { + this.highlightOverlay.style.display = 'none'; + } + document.removeEventListener('mousemove', this.handleMouseMove); + document.removeEventListener('click', this.handleClick); } - }); -}, { threshold: 0.85 }); -let parseItems = function() { - document.querySelectorAll().forEach( - item => { observer.observe(item) } - ); -} + this.updateSelectionButton(); + } + + updateSelectionButton() { + if (this.selectModeButton) { + this.selectModeButton.classList.toggle('active', this.selectionMode); + this.selectModeButton.textContent = this.selectionMode ? + '🎯 Exit Selection Mode' : + '🎯 Select Price Element'; + } + } + + handleMouseMove(e) { + if (!this.selectionMode) return; + + const element = document.elementFromPoint(e.clientX, e.clientY); + if (!element) return; + + const priceElement = this.findPriceElement(element); + if (priceElement && this.highlightOverlay) { + const rect = priceElement.getBoundingClientRect(); + this.highlightOverlay.style.display = 'block'; + this.highlightOverlay.style.top = `${rect.top + window.scrollY}px`; + this.highlightOverlay.style.left = `${rect.left}px`; + this.highlightOverlay.style.width = `${rect.width}px`; + this.highlightOverlay.style.height = `${rect.height}px`; + } else if (this.highlightOverlay) { + this.highlightOverlay.style.display = 'none'; + } + } + + handleClick(e) { + if (!this.selectionMode) return; + + e.preventDefault(); + e.stopPropagation(); + + const element = document.elementFromPoint(e.clientX, e.clientY); + if (!element) return; + + const priceElement = this.findPriceElement(element); + if (priceElement) { + this.convertPrice(priceElement); + if (this.highlightOverlay) { + this.highlightOverlay.style.display = 'none'; + } + + this.toggleSelectionMode(false); + } + } -//loot at https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver provide to watch if chanbge being made to the DOM element -setTimeout(parseItems, 5000); \ No newline at end of file + findPriceElement(element) { + let current = element; + for (let i = 0; i < 4; i++) { + if (!current) break; + if (this.extractPrice(current.textContent)) { + return current; + } + current = current.parentElement; + } + return null; + } + + setupMutationObserver() { + if (!document.body) return; + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'childList' || mutation.type === 'characterData') { + this.convertedElements.forEach((originalData, element) => { + if (document.contains(element)) { + this.updateConvertedPrice(element, originalData); + } else { + this.convertedElements.delete(element); + } + }); + } + }); + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + characterData: true + }); + } + + extractPrice(text) { + if (!text) return null; + const priceRegex = /[\$€£¥]?\s*\d+([.,]\d{1,2})?|\d+([.,]\d{1,2})?\s*[\$€£¥]/; + const match = text.match(priceRegex); + if (!match) return null; + + const price = match[0].replace(/[^\d.,]/g, '').replace(',', '.'); + const value = parseFloat(price); + return isNaN(value) ? null : value; + } + + async convertPrice(element) { + if (!this.rates || !element) return; + + const originalText = element.textContent.trim(); + const price = this.extractPrice(originalText); + + if (!price) return; + + if (!this.convertedElements.has(element)) { + this.convertedElements.set(element, { + price, + text: originalText, + currency: this.fromCurrency + }); + } + + this.updateConvertedPrice(element, this.convertedElements.get(element)); + } + + updateConvertedPrice(element, originalData) { + if (!this.rates || !element || !originalData) return; + + const rate = this.rates[this.toCurrency] / this.rates[originalData.currency]; + const convertedPrice = originalData.price * rate; + + const formattedPrice = new Intl.NumberFormat(undefined, { + style: 'currency', + currency: this.toCurrency + }).format(convertedPrice); + + element.textContent = formattedPrice; + } + + showDropdown(type) { + if (!this.dropdown || !this.rates) return; + + const box = type === 'from' ? this.fromBox : this.toBox; + const rect = box.getBoundingClientRect(); + + this.dropdown.style.top = `${rect.bottom}px`; + this.dropdown.style.left = `${rect.left}px`; + this.dropdown.style.display = 'block'; + + this.dropdown.innerHTML = Object.keys(this.rates).map(currency => ` +
+ ${currency} +
+ `).join(''); + + this.dropdown.querySelectorAll('.currency-option').forEach(option => { + option.addEventListener('click', (e) => this.handleCurrencyChange(e)); + }); + } + + handleCurrencyChange(e) { + const currency = e.target.dataset.currency; + const type = e.target.dataset.type; + + if (type === 'from') { + this.fromCurrency = currency; + } else { + this.toCurrency = currency; + } + + this.updatePopupUI(); + if (this.dropdown) { + this.dropdown.style.display = 'none'; + } + + chrome.storage.local.set({ + fromCurrency: this.fromCurrency, + toCurrency: this.toCurrency + }); + } + + handleSwitchCurrencies() { + [this.fromCurrency, this.toCurrency] = [this.toCurrency, this.fromCurrency]; + this.updatePopupUI(); + + chrome.storage.local.set({ + fromCurrency: this.fromCurrency, + toCurrency: this.toCurrency + }); + } + + updatePopupUI() { + if (!this.rates || !this.fromBox || !this.toBox || !this.rateInfo) return; + + const rate = this.rates[this.toCurrency] / this.rates[this.fromCurrency]; + const convertedAmount = this.amount * rate; + + this.fromBox.querySelector('.currency-code').textContent = this.fromCurrency; + this.fromBox.querySelector('.currency-amount').textContent = this.amount.toFixed(2); + + this.toBox.querySelector('.currency-code').textContent = this.toCurrency; + this.toBox.querySelector('.currency-amount').textContent = convertedAmount.toFixed(2); + + this.rateInfo.textContent = `1 ${this.fromCurrency} = ${rate.toFixed(2)} ${this.toCurrency}`; + } +} + +if (document.body) { + const converter = new DOMCurrencyConverter(); +} else { + document.addEventListener('DOMContentLoaded', () => { + new DOMCurrencyConverter(); + }); +} \ No newline at end of file diff --git a/manifest.json b/manifest.json index 90c7b68..9989def 100644 --- a/manifest.json +++ b/manifest.json @@ -1,22 +1,29 @@ { "manifest_version": 3, - "icons": { "128": "assets/img/AM.png" }, - "name": "AM_Rate_Currency", - "description": "Auto Converting currency", - "version": "1.9.2", + "name": "Currency Converter", + "description": "Automatically convert currencies on any webpage", + "version": "1.0.0", + "icons": { + "128": "assets/img/Menu.jpg" + }, "action": { - "default_icon": "assets/img/AM.png" + "default_popup": "popup.html" }, "content_scripts": [ { - "matches": ["https://*/*"], - "css": ["assets/css/style.css"], - "js": ["assets/js/app.js"] + "matches": [""], + "js": ["assets/js/app.js"], + "run_at": "document_end" } ], - "permissions": ["storage","activeTab","declarativeContent","scripting"], - "content_security_policy": { - "script-src": "self", - "object-src": "self" - } + "permissions": [ + "storage", + "activeTab", + "scripting", + "tabs" + ], + "host_permissions": [ + "https://api.exchangerate-api.com/*", + "" + ] } diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..b295466 --- /dev/null +++ b/popup.html @@ -0,0 +1,190 @@ + + + + + + + + + +
+
+
USD
+
1.00
+
+ + + +
+
RUB
+
60.00
+
+ +
1 USD = 60.00 RUB
+ + +
+
+ + + + \ No newline at end of file diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..133b93c --- /dev/null +++ b/popup.js @@ -0,0 +1,172 @@ +class CurrencyConverter { + constructor() { + this.rates = null; + this.fromCurrency = 'USD'; + this.toCurrency = 'RUB'; + this.amount = 1; + this.lastFetch = null; + this.selectionMode = false; + + this.initializeElements(); + this.loadRates(); + this.setupEventListeners(); + this.loadPreferences(); + this.setupMessageListener(); + } + + initializeElements() { + this.fromBox = document.getElementById('fromCurrency'); + this.toBox = document.getElementById('toCurrency'); + this.switchButton = document.querySelector('.switch-button'); + this.dropdown = document.getElementById('currencyDropdown'); + this.rateInfo = document.querySelector('.rate-info'); + this.selectModeButton = document.getElementById('selectModeButton'); + } + + setupMessageListener() { + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.action === 'selectionModeChanged') { + this.selectionMode = message.selectionMode; + this.updateSelectionButton(); + } + return true; + }); + } + + updateSelectionButton() { + this.selectModeButton.classList.toggle('active', this.selectionMode); + this.selectModeButton.textContent = this.selectionMode ? + '🎯 Exit Selection Mode' : + '🎯 Select Price Element'; + } + + async loadPreferences() { + const prefs = await chrome.storage.local.get(['fromCurrency', 'toCurrency']); + if (prefs.fromCurrency) { + this.fromCurrency = prefs.fromCurrency; + this.fromBox.querySelector('.currency-code').textContent = prefs.fromCurrency; + } + if (prefs.toCurrency) { + this.toCurrency = prefs.toCurrency; + this.toBox.querySelector('.currency-code').textContent = prefs.toCurrency; + } + this.updateConversion(); + } + + async loadRates() { + try { + const stored = localStorage.getItem('currencyRates'); + const storedTime = localStorage.getItem('lastFetchTime'); + + if (stored && storedTime) { + const timeDiff = Date.now() - parseInt(storedTime); + if (timeDiff < 24 * 60 * 60 * 1000) { + this.rates = JSON.parse(stored); + this.updateConversion(); + return; + } + } + + const response = await fetch('https://api.exchangerate-api.com/v4/latest/USD'); + const data = await response.json(); + this.rates = data.rates; + localStorage.setItem('currencyRates', JSON.stringify(this.rates)); + localStorage.setItem('lastFetchTime', Date.now().toString()); + + this.updateConversion(); + } catch (error) { + console.error('Error fetching rates:', error); + } + } + + setupEventListeners() { + this.fromBox.addEventListener('click', () => this.showDropdown('from')); + this.toBox.addEventListener('click', () => this.showDropdown('to')); + this.switchButton.addEventListener('click', () => this.switchCurrencies()); + this.selectModeButton.addEventListener('click', () => this.toggleSelectionMode()); + + document.addEventListener('click', (e) => { + if (!this.dropdown.contains(e.target) && + !this.fromBox.contains(e.target) && + !this.toBox.contains(e.target)) { + this.dropdown.style.display = 'none'; + } + }); + } + + async toggleSelectionMode() { + this.selectionMode = !this.selectionMode; + this.updateSelectionButton(); + + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + chrome.tabs.sendMessage(tab.id, { + action: 'toggleSelection', + selectionMode: this.selectionMode + }); + } + + showDropdown(type) { + const box = type === 'from' ? this.fromBox : this.toBox; + const rect = box.getBoundingClientRect(); + + this.dropdown.style.top = `${rect.bottom}px`; + this.dropdown.style.left = `${rect.left}px`; + this.dropdown.style.display = 'block'; + + this.dropdown.innerHTML = Object.keys(this.rates).map(currency => ` +
+ ${currency} +
+ `).join(''); + + this.dropdown.querySelectorAll('.currency-option').forEach(option => { + option.addEventListener('click', (e) => { + const currency = e.target.dataset.currency; + const type = e.target.dataset.type; + + if (type === 'from') { + this.fromCurrency = currency; + } else { + this.toCurrency = currency; + } + + this.updateConversion(); + this.dropdown.style.display = 'none'; + + chrome.storage.local.set({ + fromCurrency: this.fromCurrency, + toCurrency: this.toCurrency + }); + }); + }); + } + + switchCurrencies() { + [this.fromCurrency, this.toCurrency] = [this.toCurrency, this.fromCurrency]; + this.updateConversion(); + + chrome.storage.local.set({ + fromCurrency: this.fromCurrency, + toCurrency: this.toCurrency + }); + } + + updateConversion() { + if (!this.rates) return; + + const rate = this.rates[this.toCurrency] / this.rates[this.fromCurrency]; + const convertedAmount = this.amount * rate; + + this.fromBox.querySelector('.currency-code').textContent = this.fromCurrency; + this.fromBox.querySelector('.currency-amount').textContent = this.amount.toFixed(2); + + this.toBox.querySelector('.currency-code').textContent = this.toCurrency; + this.toBox.querySelector('.currency-amount').textContent = convertedAmount.toFixed(2); + + this.rateInfo.textContent = `1 ${this.fromCurrency} = ${rate.toFixed(2)} ${this.toCurrency}`; + } +} + +document.addEventListener('DOMContentLoaded', () => { + new CurrencyConverter(); +});