diff --git a/src/background.ts b/src/background.ts index d5736cf..c8756df 100644 --- a/src/background.ts +++ b/src/background.ts @@ -3,6 +3,7 @@ import { BlockIndicator } from "./decorators/BlockIndicator"; import { CorrectFinalPrice } from "./decorators/CorrectFinalPrice"; import { PriceCheckerIndicator } from "./decorators/PriceCheckerIndicator"; import { Language } from "./enums/Language"; +import { DarkModeHandler } from "./handlers/darkModeHandler"; import { PromotionalVideoHandler } from "./handlers/promotionalVideoHandler"; import { SponsoredFBTHandler } from "./handlers/sponsoredFBTHandler"; import { SponsoredProductHandler } from "./handlers/sponsoredProductHandler"; @@ -13,11 +14,12 @@ import { retrieveVisibility } from "./retrievers/visibilityRetriever"; import { State } from "./types/State"; const state: State = { - visible: true, - language: Language.ENGLISH, - sponsoredCount: 0, - sponsoredShelfCount: 0, - videoCount: 0, + visible: true, + language: Language.ENGLISH, + sponsoredCount: 0, + sponsoredShelfCount: 0, + videoCount: 0, + darkMode: false, }; const sponsoredShelfHandler = new SponsoredShelfHandler(state); @@ -25,77 +27,80 @@ const promotionalVideoHandler = new PromotionalVideoHandler(state); const sponsoredProductHandler = new SponsoredProductHandler(state); const sponsoredProductListHandler = new SponsoredProductListHandler(state); const sponsoredFBTHandler = new SponsoredFBTHandler(state); +const darkModeHandler = new DarkModeHandler(state); const blockIndicator = new BlockIndicator(state); const btsIndicator = new PriceCheckerIndicator(state); const correctFinalPrice = new CorrectFinalPrice(state); (function () { - async function initializer() { - state.visible = retrieveVisibility(); - state.language = retrieveLanguage(); + async function initializer() { + state.visible = retrieveVisibility(); + state.language = retrieveLanguage(); - flagContent(); - await flagAdditionalContent(); + document.body.appendChild(darkModeHandler.createDarkModeToggle()); - blockIndicator.addOrUpdate(); - } + flagContent(); + await flagAdditionalContent(); - function flagContent() { - promotionalVideoHandler.flag(); - sponsoredShelfHandler.flag(); - sponsoredProductHandler.flag(); - sponsoredProductListHandler.flag(); - sponsoredFBTHandler.flag(); - } + blockIndicator.addOrUpdate(); + } - async function flagAdditionalContent() { - toggleContentVisibility(state); - await btsIndicator.start(); - await correctFinalPrice.start(); - } + function flagContent() { + promotionalVideoHandler.flag(); + sponsoredShelfHandler.flag(); + sponsoredProductHandler.flag(); + sponsoredProductListHandler.flag(); + sponsoredFBTHandler.flag(); + } + + async function flagAdditionalContent() { + toggleContentVisibility(state); + await btsIndicator.start(); + await correctFinalPrice.start(); + } - function observeMutations() { - const observer1 = new MutationObserver(() => flagContent()); - const observer2 = new MutationObserver( - (mutationsList: MutationRecord[]) => { - for (const mutation of mutationsList) { - if ( - mutation.type === "attributes" && + function observeMutations() { + const observer1 = new MutationObserver(() => flagContent()); + const observer2 = new MutationObserver( + (mutationsList: MutationRecord[]) => { + for (const mutation of mutationsList) { + if ( + mutation.type === "attributes" && mutation.attributeName === "id" - ) { - blockIndicator.addOrUpdate(); - } - } - } - ); + ) { + blockIndicator.addOrUpdate(); + } + } + } + ); - observer1.observe(document.body, { childList: true, subtree: true }); - observer2.observe(document.body, { attributes: true }); - } + observer1.observe(document.body, { childList: true, subtree: true }); + observer2.observe(document.body, { attributes: true }); + } - window.onload = async function () { - await initializer(); - observeMutations(); - }; + window.onload = async function () { + await initializer(); + observeMutations(); + }; })(); chrome.runtime.onMessage.addListener( - ( - request: { action: string }, - sender: chrome.runtime.MessageSender, - sendResponse: (response: { + ( + request: { action: string }, + sender: chrome.runtime.MessageSender, + sendResponse: (response: { sponsoredCount: number; sponsoredShelfCount: number; videoCount: number; }) => void - ) => { - if (request.action === "getCount") { - sendResponse({ - sponsoredCount: state.sponsoredCount, - sponsoredShelfCount: state.sponsoredShelfCount, - videoCount: state.videoCount, - }); - } + ) => { + if (request.action === "getCount") { + sendResponse({ + sponsoredCount: state.sponsoredCount, + sponsoredShelfCount: state.sponsoredShelfCount, + videoCount: state.videoCount, + }); } + } ); diff --git a/src/css/darkModeToggle.css b/src/css/darkModeToggle.css new file mode 100644 index 0000000..a5fbfa0 --- /dev/null +++ b/src/css/darkModeToggle.css @@ -0,0 +1,28 @@ +.dark-mode-toggle { + display: flex !important; + align-items: center !important; + justify-content: center !important; + width: 50px !important; + height: 50px !important; + padding: 8px !important; + border: 1px solid #bbbbbb !important; + border-radius: 50% !important; + background: #55868c !important; + color: #ffffff !important; + cursor: pointer !important; + position: fixed !important; + bottom: 20px !important; + left: 20px !important; + z-index: 9999 !important; +} + +.dark-mode .dark-mode-toggle { + border-color: var(--dark-mode-border) !important; + color: var(--dark-mode-text) !important; +} + +.dark-mode-toggle svg { + width: 24px !important; + height: 24px !important; + margin: 0 !important; +} diff --git a/src/css/style.css b/src/css/style.css index b82bfcf..a778e7a 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -1,8 +1,200 @@ :root { - --custom-light-red: #ff0000; + --custom-light-red: #ff0000 !important; --flagged-item-box-shadow: 0px 0px 5px 2px var(--custom-light-red) !important; + + --dark-mode-bg: #1a1a1a !important; + --dark-mode-text: #ffffff !important; + --dark-mode-border: #404040 !important; + --dark-mode-hover: #2d2d2d !important; + --dark-mode-sponsored: #3d0000 !important; + --dark-mode-sponsored-text: #ff6b6b !important; + --dark-mode-button: #2d2d2d !important; + --dark-mode-button-text: #d0d0d0 !important; +} + +body { + transition: background-color 0.3s ease, color 0.3s ease !important; +} + +body.dark-mode, +.dark-mode body { + background-color: var(--color-background-base) !important; + color: var(--color-text-primary) !important; +} + +html.dark-mode, +.dark-mode html { + background-color: var(--color-background-base) !important; +} + +.dark-mode * { + background-color: transparent !important; +} + +.dark-mode { + --color-text-primary: #ffffff !important; + --color-text-secondary: #cccccc !important; + --color-text-muted: #999999 !important; + + --color-background-base: #121212 !important; + --color-background-secondary: #1a1a1a !important; + --color-background-highlight: #252525 !important; +} + +.dark-mode body, +.dark-mode * { + color: var(--color-text-primary) !important; } +.dark-mode p, +.dark-mode li, +.dark-mode span, +.dark-mode a { + color: var(--color-text-secondary) !important; +} + +.dark-mode a { + text-decoration: none; + color: var(--color-link) !important; +} + +.dark-mode a:hover { + color: var(--color-link-hover) !important; +} + +.dark-mode .muted-text, +.dark-mode .low-contrast-text { + color: var(--color-text-muted) !important; +} + +.dark-mode button { + color: var(--color-button-text) !important; +} + +.dark-mode h1, +.dark-mode h2, +.dark-mode h3, +.dark-mode h4, +.dark-mode h5, +.dark-mode h6 { + color: var(--color-text-primary) !important; + font-weight: bold; +} + +.dark-mode { + --color-background-base: #121212 !important; + --color-background-secondary: #1e1e1e !important; + --color-background-highlight: #252525 !important; + --color-background-base-0: #121212 !important; + --color-background-neutral-0: #181818 !important; + --color-background-neutral-1: #1f1f1f !important; + --color-background-neutral-2: #252525 !important; + --color-background-neutral-3: #2c2c2c !important; + --color-background-neutral-4: #333333 !important; + --color-background-neutral-5: #3b3b3b !important; + --color-background-neutral-6: #444444 !important; + --color-background-neutral-7: #4d4d4d !important; + --color-background-neutral-8: #555555 !important; + --color-background-neutral-9: #666666 !important; + --color-background-neutral-10: #777777 !important; + --color-text-neutral-0: #e0e0e0 !important; + --color-text-neutral-1: #d4d4d4 !important; + --color-text-neutral-2: #c8c8c8 !important; + --color-text-neutral-3: #bcbcbc !important; + --color-text-neutral-4: #b0b0b0 !important; + --color-text-neutral-5: #a4a4a4 !important; + --color-text-neutral-6: #989898 !important; + --color-text-neutral-7: #8c8c8c !important; + --color-text-neutral-8: #808080 !important; + --color-text-neutral-9: #747474 !important; + --color-text-neutral-10: #686868 !important; + --color-border-neutral-1: #2c2c2c !important; + --color-border-neutral-2: #3c3c3c !important; + --color-border-neutral-3: #4c4c4c !important; + --color-border-neutral-4: #5c5c5c !important; + --color-border-neutral-5: #6c6c6c !important; + --color-border-neutral-6: #7c7c7c !important; + --color-border-neutral-7: #8c8c8c !important; + + --color-text-primary: #ffffff !important; + --color-text-secondary: #b0b0b0 !important; + --color-text-highlight: #ffcc00 !important; + + --color-border-primary: #3a3a3a !important; + --color-border-highlight: #ffcc00 !important; + + --color-link: #1e90ff !important; + --color-link-hover: #6495ed !important; + + --color-button-background: #3a3a3a !important; + --color-button-text: #ffffff !important; + --color-button-hover: #555555 !important; + + --color-card-background: #1f1f1f !important; + --color-card-border: #333333 !important; + --color-card-text: #e0e0e0 !important; +} + +body.dark-mode { + background-color: var(--color-background-base) !important; + color: var(--color-text-primary) !important; +} + +a { + color: var(--color-link) !important; +} + +a:hover { + color: var(--color-link-hover) !important; +} + +button { + background-color: var(--color-button-background) !important; + color: var(--color-button-text) !important; + border: 1px solid var(--color-border-primary) !important; +} + +button:hover { + background-color: var(--color-button-hover) !important; +} + +.card { + background-color: var(--color-card-background) !important; + color: var(--color-card-text) !important; + border: 1px solid var(--color-card-border) !important; +} + +header { + background-color: var(--color-background-highlight) !important; + color: var(--color-text-primary) !important; +} + +.tooltip { + background-color: var(--color-background-secondary) !important; + color: var(--color-text-secondary) !important; + border: 1px solid var(--color-border-primary) !important; +} + +.modal { + background-color: var(--color-card-background) !important; + color: var(--color-card-text) !important; + border: 1px solid var(--color-border-highlight) !important; +} + +.dark-mode .sku-info-bar, +.dark-mode .sku-card-favorite, +.dark-mode .slider-btn, +.dark-mode .suggestions-list-wrapper, +.dark-mode .bottom-drawer-content, +.dark-mode .notification, +.dark-mode .remain-sticky { + background-color: var(--color-card-background) !important; + color: var(--color-text-primary) !important; + border-bottom: 1px solid var(--color-border-primary) !important; +} + +/*************************************************************************************************************/ + .display-none { display: none !important; } @@ -98,7 +290,7 @@ text-black { font-size: 16px !important; font-weight: 500 !important; background-color: var(--color-text-link-marketplace-visited) !important; - color: #FFFFFF !important; + color: #ffffff !important; } .info-label-negative { @@ -107,7 +299,7 @@ text-black { font-size: 16px !important; font-weight: 500 !important; background-color: var(--color-text-link-danger-visited) !important; - color: #FFFFFF !important; + color: #ffffff !important; } .sponsored-label svg, @@ -152,4 +344,4 @@ text-black { font-size: 16px; font-weight: 600 !important; padding-left: 8px; -} \ No newline at end of file +} diff --git a/src/decorators/PriceCheckerIndicator.ts b/src/decorators/PriceCheckerIndicator.ts index 83572c2..39b888e 100644 --- a/src/decorators/PriceCheckerIndicator.ts +++ b/src/decorators/PriceCheckerIndicator.ts @@ -1,187 +1,225 @@ import { Language } from "../enums/Language"; import { buyThroughSkroutzShippingCostRetriever } from "../retrievers/buyThroughSkroutzShippingCostRetriever"; import { buyThroughSkroutzRetriever } from "../retrievers/buyThroughSkroutzRetriever"; -import { LowestPriceData, marketDataReceiver } from "../retrievers/marketDataRetriever"; +import { + LowestPriceData, + marketDataReceiver, +} from "../retrievers/marketDataRetriever"; import { State } from "../types/State"; - - +import { appendCreditChild } from "../functions/appendCreditChild"; function roundToZero(value: number, precision = 1e-10) { - return Math.abs(value) < precision ? 0 : value; + return Math.abs(value) < precision ? 0 : value; } export class PriceCheckerIndicator { - private state: State; - private btsPrice: number | undefined = undefined; - private btsShippingCost: number | undefined = undefined; - private lowestPriceData: LowestPriceData | undefined = undefined; + private state: State; + private btsPrice: number | undefined = undefined; + private btsShippingCost: number | undefined = undefined; + private lowestPriceData: LowestPriceData | undefined = undefined; - constructor(state: State) { - this.state = state; - } + constructor(state: State) { + this.state = state; + } - public async start() { - const offeringCard = document.querySelector("article.offering-card"); + public async start() { + const offeringCard = document.querySelector("article.offering-card"); - if (!offeringCard) { - return; - } - - this.lowestPriceData = await marketDataReceiver(); - if (!this.lowestPriceData) { - return; - } + if (!offeringCard) { + return; + } - this.btsPrice = buyThroughSkroutzRetriever(); - this.btsShippingCost = buyThroughSkroutzShippingCostRetriever(); - this.insertPriceIndication(offeringCard); + this.lowestPriceData = await marketDataReceiver(); + if (!this.lowestPriceData) { + return; } - private insertPriceIndication(element: Element): void { - const priceIndication = this.createPriceIndicationElement(); - element.insertBefore(priceIndication, element.children[1]); + this.btsPrice = buyThroughSkroutzRetriever(); + this.btsShippingCost = buyThroughSkroutzShippingCostRetriever(); + this.insertPriceIndication(offeringCard); + } + + private insertPriceIndication(element: Element): void { + const priceIndication = this.createPriceIndicationElement(); + element.insertBefore(priceIndication, element.children[1]); + } + + private createPriceIndicationElement(): HTMLDivElement { + const priceIndication = document.createElement("div"); + const colFlex1 = document.createElement("div"); + const colFlex2 = document.createElement("div"); + const rowFlex1 = document.createElement("div"); + const rowFlex2 = document.createElement("div"); + const rowFlex3 = document.createElement("div"); + const otherLowestPrice = document.createElement("div"); + + const shippingCost = this.btsShippingCost ?? 0; + let isLowestPrice = false; + if (!!this.btsPrice && !!this.lowestPriceData) { + isLowestPrice = + this.btsPrice + shippingCost <= this.lowestPriceData.lowestTotalPrice; } - private createPriceIndicationElement(): HTMLDivElement { - const priceIndication = document.createElement("div"); - const colFlex1 = document.createElement("div"); - const colFlex2 = document.createElement("div"); - const rowFlex1 = document.createElement("div"); - const rowFlex2 = document.createElement("div"); - const rowFlex3 = document.createElement("div"); - const otherLowestPrice = document.createElement("div"); - - const shippingCost = this.btsShippingCost ?? 0; - let isLowestPrice = false; - if (!!this.btsPrice && !!this.lowestPriceData) { - isLowestPrice = this.btsPrice + shippingCost <= this.lowestPriceData.lowestTotalPrice; - } - - const checkerStyle = isLowestPrice ? "info-label-positive" : "info-label-negative"; - - priceIndication.classList.add("display-padding", "inline-flex-row", "price-checker-outline", checkerStyle); - colFlex1.classList.add("inline-flex-col"); - colFlex2.classList.add("inline-flex-col"); - rowFlex1.classList.add("inline-flex-row"); - rowFlex2.classList.add("inline-flex-row"); - rowFlex3.classList.add("inline-flex-row"); - otherLowestPrice.classList.add("price"); - - const priceComma = document.createElement("span"); - const priceDecimal = document.createElement("span"); - const currencySymbol = document.createElement("span"); - const loyaltyPoints = document.createElement("span"); - const shippingInfo = document.createElement("span"); - - const lowestPrice = this.lowestPriceData ? this.lowestPriceData.lowestTotalPrice : undefined; - const [integerPart, decimalPart] = (this.lowestPriceData?.lowestProductPrice?.toFixed(2) ?? "?").split("."); - - priceComma.textContent = ","; - priceDecimal.textContent = decimalPart; - currencySymbol.textContent = "€"; - - priceComma.classList.add("comma"); - - otherLowestPrice.textContent = integerPart; - otherLowestPrice.appendChild(priceComma); - otherLowestPrice.appendChild(priceDecimal); - otherLowestPrice.appendChild(currencySymbol); - otherLowestPrice.appendChild(loyaltyPoints); - otherLowestPrice.appendChild(shippingInfo); - - const priceDifference = document.createElement("span"); - const priceDifferenceExplanation = document.createElement("span"); - - const shippingCostSpan = document.createElement("span"); - const shippingCostExplanationSpan = document.createElement("span"); - - const diff = roundToZero((lowestPrice ?? 0) - (this.btsPrice ?? 0) - shippingCost); - - const isLowestPriceFreeShipping = this.lowestPriceData?.lowestShippingCost === 0; - console.log("this.lowestPriceData?.lowestShippingCost :>> ", this.lowestPriceData?.lowestShippingCost); - console.log("isLowestPriceFreeShipping :>> ", isLowestPriceFreeShipping); - if (diff > 0) { - if (isLowestPriceFreeShipping) { - shippingCostSpan.textContent = this.state.language === Language.ENGLISH ? "Free shipping" : "Δωρεάν μεταφορικά"; - } else { - shippingCostSpan.textContent = ` + ${this.lowestPriceData?.lowestShippingCost}€`; - shippingCostExplanationSpan.textContent = this.state.language === Language.ENGLISH - ? " shipping cost" - : " μεταφορικά"; - } - - priceDifference.textContent = ` ${diff.toFixed(2)}€`; - priceDifferenceExplanation.textContent = this.state.language === Language.ENGLISH - ? " more expensive" - : " ακριβότερο"; - } else if (diff < 0) { - if (isLowestPriceFreeShipping) { - shippingCostSpan.textContent = this.state.language === Language.ENGLISH ? "Free shipping" : "Δωρεάν μεταφορικά"; - } else { - shippingCostSpan.textContent = ` + ${this.lowestPriceData?.lowestShippingCost}€`; - shippingCostExplanationSpan.textContent = this.state.language === Language.ENGLISH - ? " shipping cost" - : " μεταφορικά"; - } - - priceDifference.textContent = ` ${diff.toFixed(2)}€`; - priceDifferenceExplanation.textContent = this.state.language === Language.ENGLISH - ? " cheaper" - : " φτηνότερο"; - } - - rowFlex2.appendChild(shippingCostSpan); - rowFlex2.appendChild(shippingCostExplanationSpan); - - rowFlex3.appendChild(priceDifference); - rowFlex3.appendChild(priceDifferenceExplanation); - - colFlex2.appendChild(rowFlex2); - colFlex2.appendChild(rowFlex3); - - rowFlex1.appendChild(otherLowestPrice); - rowFlex1.appendChild(colFlex2); - - colFlex1.appendChild(rowFlex1); - - const information = document.createElement("div"); - information.textContent = this.state.language === Language.ENGLISH - ? "is the lowest price with shipping apart from \"Buy through Skroutz\"" - : "είναι η χαμηλότερη τιμή με μεταφορικά εκτός \"Αγορά μέσω Skroutz\""; - information.classList.add("align-center", "font-bold"); - - colFlex1.appendChild(information); - - const goToStoreButton = this.goToStoreButtonCreator(isLowestPrice); - colFlex1.appendChild(goToStoreButton); - - priceIndication.title = this.state.language === Language.ENGLISH - ? `(note that "Buy through Skroutz" is ${this.btsPrice}€ + ${shippingCost}€ shipping)` - : `(σημειώστε ότι "Αγορά μέσω Skroutz" είναι ${this.btsPrice}€ + ${shippingCost}€ μεταφορικά)`; - priceIndication.appendChild(colFlex1); - - return priceIndication; + const checkerStyle = isLowestPrice + ? "info-label-positive" + : "info-label-negative"; + + priceIndication.classList.add( + "display-padding", + "inline-flex-row", + "price-checker-outline", + checkerStyle + ); + colFlex1.classList.add("inline-flex-col"); + colFlex2.classList.add("inline-flex-col"); + rowFlex1.classList.add("inline-flex-row"); + rowFlex2.classList.add("inline-flex-row"); + rowFlex3.classList.add("inline-flex-row"); + otherLowestPrice.classList.add("price"); + + const priceComma = document.createElement("span"); + const priceDecimal = document.createElement("span"); + const currencySymbol = document.createElement("span"); + const loyaltyPoints = document.createElement("span"); + const shippingInfo = document.createElement("span"); + + const lowestPrice = this.lowestPriceData + ? this.lowestPriceData.lowestTotalPrice + : undefined; + const [integerPart, decimalPart] = ( + this.lowestPriceData?.lowestProductPrice?.toFixed(2) ?? "?" + ).split("."); + + priceComma.textContent = ","; + priceDecimal.textContent = decimalPart; + currencySymbol.textContent = "€"; + + priceComma.classList.add("comma"); + + otherLowestPrice.textContent = integerPart; + otherLowestPrice.appendChild(priceComma); + otherLowestPrice.appendChild(priceDecimal); + otherLowestPrice.appendChild(currencySymbol); + otherLowestPrice.appendChild(loyaltyPoints); + otherLowestPrice.appendChild(shippingInfo); + + const priceDifference = document.createElement("span"); + const priceDifferenceExplanation = document.createElement("span"); + + const shippingCostSpan = document.createElement("span"); + const shippingCostExplanationSpan = document.createElement("span"); + + const diff = roundToZero( + (lowestPrice ?? 0) - (this.btsPrice ?? 0) - shippingCost + ); + + const isLowestPriceFreeShipping = + this.lowestPriceData?.lowestShippingCost === 0; + + if (diff > 0) { + if (isLowestPriceFreeShipping) { + shippingCostSpan.textContent = + this.state.language === Language.ENGLISH + ? "Free shipping" + : "Δωρεάν μεταφορικά"; + } else { + shippingCostSpan.textContent = ` + ${this.lowestPriceData?.lowestShippingCost}€`; + shippingCostExplanationSpan.textContent = + this.state.language === Language.ENGLISH + ? " shipping cost" + : " μεταφορικά"; + } + + priceDifference.textContent = ` ${diff.toFixed(2)}€`; + priceDifferenceExplanation.textContent = + this.state.language === Language.ENGLISH + ? " more expensive" + : " ακριβότερο"; + } else if (diff < 0) { + if (isLowestPriceFreeShipping) { + shippingCostSpan.textContent = + this.state.language === Language.ENGLISH + ? "Free shipping" + : "Δωρεάν μεταφορικά"; + } else { + shippingCostSpan.textContent = ` + ${this.lowestPriceData?.lowestShippingCost}€`; + shippingCostExplanationSpan.textContent = + this.state.language === Language.ENGLISH + ? " shipping cost" + : " μεταφορικά"; + } + + priceDifference.textContent = ` ${diff.toFixed(2)}€`; + priceDifferenceExplanation.textContent = + this.state.language === Language.ENGLISH ? " cheaper" : " φτηνότερο"; } - private goToStoreButtonCreator(isLowestPrice: boolean): HTMLButtonElement { - const goToStoreButton = document.createElement("button"); - const buttonStyle = isLowestPrice ? "go-to-shop-button-positive" : "go-to-shop-button-negative"; + rowFlex2.appendChild(shippingCostSpan); + rowFlex2.appendChild(shippingCostExplanationSpan); + + rowFlex3.appendChild(priceDifference); + rowFlex3.appendChild(priceDifferenceExplanation); + + colFlex2.appendChild(rowFlex2); + colFlex2.appendChild(rowFlex3); + + rowFlex1.appendChild(otherLowestPrice); + rowFlex1.appendChild(colFlex2); + + colFlex1.appendChild(rowFlex1); + const information = document.createElement("div"); + information.classList.add("align-center", "font-bold"); - goToStoreButton.classList.add(buttonStyle); - goToStoreButton.textContent = this.state.language === Language.ENGLISH - ? "Go to Shop" - : "Μετάβαση στο κατάστημα"; + information.textContent = + this.state.language === Language.ENGLISH + ? 'is the lowest price with shipping apart from "Buy through Skroutz"' + : 'είναι η χαμηλότερη τιμή με μεταφορικά εκτός "Αγορά μέσω Skroutz"'; + information.classList.add("align-center", "font-bold"); - goToStoreButton.addEventListener("click", () => { - const targetId = `shop-${this.lowestPriceData?.shopId}`; - const targetElement = document.getElementById(targetId); + colFlex1.appendChild(information); + appendCreditChild(colFlex1); - if (targetElement) { - targetElement.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" }); - targetElement.classList.add("lowest-price-store-highlight"); - } + const goToStoreButton = this.goToStoreButtonCreator(isLowestPrice); + colFlex1.appendChild(goToStoreButton); + + priceIndication.title = + this.state.language === Language.ENGLISH + ? `(note that "Buy through Skroutz" is ${this.btsPrice}€ + ${shippingCost}€ shipping)` + : `(σημειώστε ότι "Αγορά μέσω Skroutz" είναι ${this.btsPrice}€ + ${shippingCost}€ μεταφορικά)`; + priceIndication.appendChild(colFlex1); + + return priceIndication; + } + + private goToStoreButtonCreator(isLowestPrice: boolean): HTMLButtonElement { + const goToStoreButton = document.createElement("button"); + const buttonStyle = isLowestPrice + ? "go-to-shop-button-positive" + : "go-to-shop-button-negative"; + + goToStoreButton.classList.add(buttonStyle); + goToStoreButton.textContent = + this.state.language === Language.ENGLISH + ? "Go to Shop" + : "Μετάβαση στο κατάστημα"; + + goToStoreButton.addEventListener("click", () => { + const targetId = `shop-${this.lowestPriceData?.shopId}`; + const targetElements = document.querySelectorAll(`#${targetId}`); + + if (targetElements.length > 0) { + const targetElement = + targetElements.length > 1 ? targetElements[1] : targetElements[0]; + + targetElement.scrollIntoView({ + behavior: "smooth", + block: "center", + inline: "center", }); + targetElement.classList.add("lowest-price-store-highlight"); + } + }); - return goToStoreButton; - } + return goToStoreButton; + } } diff --git a/src/functions/appendCreditChild.ts b/src/functions/appendCreditChild.ts new file mode 100644 index 0000000..9d6bd15 --- /dev/null +++ b/src/functions/appendCreditChild.ts @@ -0,0 +1,17 @@ +import { appendLogoChild } from "./appendLogoChild"; + +export function appendCreditChild(element: HTMLDivElement | HTMLButtonElement) { + const brand = document.createElement("div"); + const brandLink = document.createElement("a"); + + brand.classList.add("icon-border", "font-bold"); + + brandLink.href = "https://paypal.me/tsiakkas"; + brandLink.textContent = "by reSkroutzed"; + brandLink.classList.add("icon-border", "font-bold"); + + brand.appendChild(brandLink); + appendLogoChild(brand); + + element.appendChild(brand); +} diff --git a/src/functions/appendLogoChild.ts b/src/functions/appendLogoChild.ts new file mode 100644 index 0000000..3ca385f --- /dev/null +++ b/src/functions/appendLogoChild.ts @@ -0,0 +1,25 @@ +export function appendLogoChild(element: HTMLDivElement | HTMLButtonElement) { + const icon = document.createElement("div"); + + icon.classList.add("align-center", "icon-border"); + + const svgElement = document.createElementNS( + "http://www.w3.org/2000/svg", + "svg" + ); + + svgElement.setAttribute("viewBox", "0 96 960 960"); + svgElement.setAttribute("width", "18"); + svgElement.setAttribute("height", "18"); + + const img = document.createElement("img"); + img.src = + "https://raw.githubusercontent.com/keybraker/reskroutzed/main/src/assets/icons/128.png"; + img.alt = "reSkroutzed"; + img.width = 18; + img.height = 18; + + icon.appendChild(img); + + element.appendChild(icon); +} diff --git a/src/handlers/darkModeHandler.ts b/src/handlers/darkModeHandler.ts new file mode 100644 index 0000000..bd4949d --- /dev/null +++ b/src/handlers/darkModeHandler.ts @@ -0,0 +1,67 @@ +import { appendLogoChild } from "../functions/appendLogoChild"; +import { State } from "../types/State"; + +export class DarkModeHandler { + private state: State; + private darkModeKey = "ssf-dark-mode"; + + constructor(state: State) { + this.state = state; + this.initializeDarkMode(); + } + + private initializeDarkMode(): void { + const darkMode = localStorage.getItem(this.darkModeKey) === "true"; + this.state.darkMode = darkMode; + this.applyDarkMode(); + } + + private applyDarkMode(): void { + if (this.state.darkMode) { + document.body.classList.add("dark-mode"); + } else { + document.body.classList.remove("dark-mode"); + } + } + + public toggleDarkMode(): void { + this.state.darkMode = !this.state.darkMode; + localStorage.setItem(this.darkModeKey, String(this.state.darkMode)); + this.applyDarkMode(); + } + + public createDarkModeToggle(): HTMLDivElement { + const button = document.createElement("button"); + const colFlex = document.createElement("div"); + + button.classList.add("dark-mode-toggle"); + colFlex.classList.add("inline-flex-col"); + + const icon = this.createDarkModeIcon(); + button.appendChild(icon); + appendLogoChild(button); + + button.addEventListener("click", () => this.toggleDarkMode()); + + colFlex.appendChild(button); + + return colFlex; + } + + private createDarkModeIcon(): SVGSVGElement { + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("viewBox", "0 0 24 24"); + svg.setAttribute("width", "16"); + svg.setAttribute("height", "16"); + + const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + path.setAttribute( + "d", + "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-3.03 0-5.5-2.47-5.5-5.5 0-1.82.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z" + ); + path.setAttribute("fill", "currentColor"); + + svg.appendChild(path); + return svg; + } +} diff --git a/src/manifest_chrome.json b/src/manifest_chrome.json index e17096c..01634fa 100644 --- a/src/manifest_chrome.json +++ b/src/manifest_chrome.json @@ -1,7 +1,7 @@ { "name": "reSkroutzed", "description": "Flags sponsored products on Skroutz.gr", - "version": "1.6.6", + "version": "1.6.7", "manifest_version": 3, "permissions": [], "host_permissions": [ @@ -17,7 +17,8 @@ "css/productFlags.css", "css/shelfFlags.css", "css/style.css", - "css/videoFlags.css" + "css/videoFlags.css", + "css/darkModeToggle.css" ], "js": [ "background.js" @@ -36,4 +37,4 @@ "48": "/assets/icons/48.png", "128": "/assets/icons/128.png" } -} \ No newline at end of file +} diff --git a/src/manifest_firefox.json b/src/manifest_firefox.json index ab63dbe..743dded 100644 --- a/src/manifest_firefox.json +++ b/src/manifest_firefox.json @@ -1,7 +1,7 @@ { "name": "reSkroutzed", "description": "Flags sponsored products on Skroutz.gr", - "version": "1.6.6", + "version": "1.6.7", "manifest_version": 3, "permissions": [], "host_permissions": [ @@ -17,7 +17,8 @@ "css/productFlags.css", "css/shelfFlags.css", "css/style.css", - "css/videoFlags.css" + "css/videoFlags.css", + "css/darkModeToggle.css" ], "js": [ "background.js" @@ -42,4 +43,4 @@ "strict_min_version": "109.0" } } -} \ No newline at end of file +} diff --git a/src/types/State.ts b/src/types/State.ts index c479e54..3e08cc7 100644 --- a/src/types/State.ts +++ b/src/types/State.ts @@ -1,9 +1,10 @@ import { Language } from "../enums/Language"; export type State = { - visible: boolean, - language: Language, - sponsoredCount: number, - sponsoredShelfCount: number, - videoCount: number, -} \ No newline at end of file + visible: boolean; + language: Language; + sponsoredCount: number; + sponsoredShelfCount: number; + videoCount: number; + darkMode: boolean; +};