diff --git a/content-scripts/components/Header.tsx b/content-scripts/components/Navbar/Header.tsx similarity index 93% rename from content-scripts/components/Header.tsx rename to content-scripts/components/Navbar/Header.tsx index 319ad7a..45b36df 100644 --- a/content-scripts/components/Header.tsx +++ b/content-scripts/components/Navbar/Header.tsx @@ -1,10 +1,11 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars import jsx from "texsaur"; +import { AuthSession } from "../../types"; import HeaderLinks from "./HeaderLinks"; import Authentication from "./HeaderAuthentication"; -import { AuthSession } from "../types"; +//TODO(thePeras): I think we should move this to a separate file dedicated to constants or a folder like `data` const HEADER_LINKS = { Estudantes: { Bolsas: "web_base.gera_pagina?p_pagina=242366", diff --git a/content-scripts/components/HeaderAuthentication.tsx b/content-scripts/components/Navbar/HeaderAuthentication.tsx similarity index 97% rename from content-scripts/components/HeaderAuthentication.tsx rename to content-scripts/components/Navbar/HeaderAuthentication.tsx index 9b03328..9886d24 100644 --- a/content-scripts/components/HeaderAuthentication.tsx +++ b/content-scripts/components/Navbar/HeaderAuthentication.tsx @@ -1,8 +1,8 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars import jsx from "texsaur"; -import { AuthSession } from "../types"; -import { togglePopover } from "../modules/utilities/popover"; -import Icon from "./Icon"; +import { AuthSession } from "../../types"; +import { togglePopover } from "../../modules/utilities/popover"; +import Icon from "../Icon"; interface Props { auth: AuthSession | null; diff --git a/content-scripts/components/HeaderLinks.tsx b/content-scripts/components/Navbar/HeaderLinks.tsx similarity index 100% rename from content-scripts/components/HeaderLinks.tsx rename to content-scripts/components/Navbar/HeaderLinks.tsx diff --git a/content-scripts/index.js b/content-scripts/index.js index d701920..58682f3 100644 --- a/content-scripts/index.js +++ b/content-scripts/index.js @@ -1,11 +1,10 @@ import { injectOverrideFunctions, reverseDateDirection, - currentAccountPage, addSortTableActions, } from "./modules/initialize"; import { injectAllChanges, userPreferences } from "./modules/options"; -import constructNewData from "./modules/utilities/constructNewData"; +import constructNewData from "./modules/options/constructNewData"; import { getStorage, setStorage } from "./modules/utilities/storage"; import { rememberLogin } from "./modules/login"; import { replaceIcons } from "./modules/icons"; @@ -18,6 +17,7 @@ import { fixPagination } from "./modules/pagination"; import { changeLayout } from "./modules/layout"; import { addStarIconToCard } from "./modules/favorite-course"; import { createComponentsPage } from "./pages/components_page"; +import { currentAccountPage } from "./pages/current_account_page"; /*-- - Docs: https://developer.chrome.com/docs/extensions/reference/storage/#synchronous-response-to-storage-updates diff --git a/content-scripts/modules/icons/constants.js b/content-scripts/modules/icons/constants.js deleted file mode 100644 index 551be22..0000000 --- a/content-scripts/modules/icons/constants.js +++ /dev/null @@ -1,240 +0,0 @@ -export const IMG_ICON_MAP = Object.freeze({ - Acreditado: "checkbox-circle", - Alerta: "alert", - Apagar: "close-circle", - Aprovado: "checkbox-circle", - Arroba: "at", - Ascendente: "arrow-up-s", - AscendenteS: "arrow-up-s", - "atalho_administracao.gif": "admin", - "atalho_computador.gif": "computer", - "atalho_coracao.gif": "heart", - "atalho_curso.gif": "honour", - "atalho_default.gif": "share-forward", - "atalho_disciplina.gif": "book", - "atalho_fabrica.gif": "building-3", - "atalho_grafico.gif": "bar-chart", - "atalho_instituicao.gif": "bank", - "atalho_laboratorio.gif": "test-tube", - "atalho_lampada.gif": "lightbulb", - "atalho_lupa.gif": "search", - "atalho_martelo.gif": "hammer", - "atalho_mundo.gif": "earth", - "atalho_olho.gif": "eye", - "atalho_pessoa.gif": "user", - "atalho_predio.gif": "building-4", - "atalho_roda_dentada.gif": "settings-4", - "atalho_smile_admirado.gif": "emotion", - "atalho_smile_feliz.gif": "emotion-happy", - "atalho_smile_triste.gif": "emotion-unhappy", - "atalho_tomada.gif": "plug", - AtencaoNPisca: "alert", - AtencaoPisca: "error-warning", - BotaoColapsar: "close-circle", - BotaoMin: "close-circle", - BotaoPersonalisar: "edit", - BotaoRestaurar: "add-circle", - Calendario: "calendar", - "CERT-Certidao": "file-paper-2", - "CERT-Visto": "checkbox-circle", - Comentario: "edit", - Completo: "checkbox-circle", - CriarNovo: "add-circle", - Descendente: "arrow-down-s", - DescendenteS: "arrow-down-s", - Documento: "file-text", - // Documento: "file-text", - DocumentoCriar: "file-add", - EditarPerfil: "edit", - EnderecoEmail: "mail", - Erro: "error-warning", - EstadoSem1_off: "arrow-up-circle", - EstadoSem1: "checkbox-circle", - EstadoSem2: "arrow-down-circle", - EstadoSem3: "refresh", - EstadoSem4: "close-circle", - "facebook_32.png": "facebook-box", - Fazer: "indeterminate-circle", - "FEST-Selecionado": "radio-button", - "FEST-Selecionar": "checkbox-blank-circle", - "GENT-ContactoAguarda": "error-warning", - "GENT-ContactoOK": "checkbox-circle", - "GPAG-Documento-Impresso": "printer", - "GPAG-Documento": "file-text", - "GPAG-MB": "bank-card-2", - "In_Logo_Web4Print_CMYK_1in.jpg": "linkedin-box", - Informa: "information", - Inscrito: "indeterminate-circle", - "instagram40x40.png": "instagram", - "IPUP-Formula": "functions", - "IT-AlocAdm": "admin", - "IT-Email": "mail", - "IT-Fotos4x4": "layout-grid", - "IT-Fotos6x6": "grid", - JanelaFechar: "close-circle", - LegendaSemaforos: "question", - Limpar: "eraser", - ListaMais: "add-circle", - ListaMenos: "close-circle", - LOV: "arrow-left", - Lupa: "search", - // Lupa: "search", - MarcaTemporal: "checkbox-circle", - MarcaTemporalActivo: "play-circle", - MarcaTemporalOff: "checkbox-blank-circle", - Mensagem: "chat-4", - MensagemBBom: "emotion-laugh", - MensagemBom: "emotion-happy", - MensagemFilhos: "question-answer", - MensagemGrande: "sticky-note", - MensagemMau: "emotion-sad", - MensagemMMau: "emotion-unhappy", - MensagemNao: "thumb-down", - MensagemNeutro: "emotion-normal", - MensagemNova: "folder-unknown", - MensagemResponder: "edit", - MensagemSemFilhos: "chat-off", - MensagemSim: "thumb-up", - MensagemVelha: "folder", - MensagemVelhaV: "folder-open", - MenuContextoDetalhesMaisG: "zoom-in", - MenuContextoDetalhesMenosG: "zoom-out", - MenuContextoEditarG: "edit", - MenuContextoImagemPisoDescerOff: "arrow-right-down", - MenuContextoImagemPisoSubirOff: "arrow-right-up", - MenuContextoInicioG: "", - MoodleIcon: undefined, - NaoInscrito: "close-circle", - Novo: "star", - Obr: "asterisk", - PaginaWeb: "global", - Pasta: "folder-open", - PastaFechada: "folder", - Pessoa: "user", - Prior1: "arrow-up-double", - Prior2: "arrow-up-s", - Prior3: "subtract", - Prior4: "arrow-down-s", - Prior5: "arrow-down-double", - QuadradoMaisPeq: "add-circle", - QuadradoMenosPeq: "close-circle", - QuadradoPequenoMais: "add-circle", - QuadradoPequenoMenos: "close-circle", - // "ResultadoActual": "map-pin", // TODO (toino): find better icon - // "ResultadoAnt": "arrow-left-s", - // "ResultadoAntFin": "skip-back", - // "ResultadoAntInt": "more", - // "ResultadoSeg": "arrow-right-s", - // "ResultadoSegFin": "skip-forward", - // "ResultadoSegInt": "more", - SemAmarelo: "indeterminate-circle", - SemPermissoes: "error-warning", - SemRegistos: "forbid", - SemVerde: "checkbox-circle", - SemVermelho: "error-warning", - SetaDir: "arrow-right", - "SUMARIOS-Anexo": "attachment", - "SUMARIOS-AnexoOff": "attachment", - "SUMARIOS-VerSumario": "file-text", - "SUMARIOS-VerSumarioOff": "file-text", - Telef: "phone", - Visto: "checkbox-circle", - "youtube_32.png": "youtube", - ZipPeq: "file-zip", -}); - -export const FA_ICON_MAP = Object.freeze({ - bars: "menu", - "ellipsis-v": "more-2", - "envelope-o": "mail", - fax: "printer", // TODO (toino): find better icon - laptop: "computer", - phone: "phone", - "plus-circle": "add-circle", - unlock: "login-circle", - envelope: "mail-unread", -}); - -export const BANNER_ICON_MAP = Object.freeze({ - alerta: "alert", - info: "information", - informa: "information", -}); - -export const BG_IMAGE_ICON_MAP = Object.freeze({ - ".acao.adicionar-elemento": "add-circle", - ".acao.adicionar": "add-circle", - ".acao.eliminar": "close-circle", - ".acao.limpar": "eraser", - ".EmAprovacao-28-14": "indeterminate-circle", - ".Impossibilidade-20-20": "error-warning", - ".Pausa-20-20": "pause", - ".terminar-sessao": "logout-circle", -}); - -export const EVENTS = Object.freeze([ - "animationcancel", - "animationend", - "animationiteration", - "animationstart", - "afterscriptexecute", - "auxclick", - "beforescriptexecute", - "blur", - "click", - "compositionend", - "compositionstart", - "compositionupdate", - "contextmenu", - "copy", - "cut", - "dblclick", - "DOMActivate", - "DOMMouseScroll", - "error", - "focusin", - "focusout", - "focus", - "fullscreenchange", - "fullscreenerror", - "gesturechange", - "gestureend", - "gesturestart", - "gotpointercapture", - "keydown", - "keypress", - "keyup", - "lostpointercapture", - "mousedown", - "mouseenter", - "mouseleave", - "mousemove", - "mouseout", - "mouseover", - "mouseup", - "mousewheel", - "paste", - "pointercancel", - "pointerdown", - "pointerenter", - "pointerleave", - "pointermove", - "pointerout", - "pointerover", - "pointerup", - "scroll", - "select", - "touchcancel", - "touchend", - "touchmove", - "touchstart", - "transitioncancel", - "transitionend", - "transitionrun", - "transitionstart", - "webkitmouseforcechanged", - "webkitmouseforcedown", - "webkitmouseforceup", - "webkitmouseforcewillbegin", - "wheel", -]); diff --git a/content-scripts/modules/icons/constants.ts b/content-scripts/modules/icons/constants.ts new file mode 100644 index 0000000..ac97bdb --- /dev/null +++ b/content-scripts/modules/icons/constants.ts @@ -0,0 +1,240 @@ +/* + NitSig is replacing the icons with new ones from RemixIcon. + Only choose icons from RemixIcon to ensure consistency. + https://remixicon.com/ +*/ + +export const IMG_ICON_MAP: { readonly [key: string]: string | undefined } = + Object.freeze({ + Acreditado: "checkbox-circle", + Alerta: "alert", + Apagar: "close-circle", + Aprovado: "checkbox-circle", + Arroba: "at", + Ascendente: "arrow-up-s", + AscendenteS: "arrow-up-s", + "atalho_administracao.gif": "admin", + "atalho_computador.gif": "computer", + "atalho_coracao.gif": "heart", + "atalho_curso.gif": "honour", + "atalho_default.gif": "share-forward", + "atalho_disciplina.gif": "book", + "atalho_fabrica.gif": "building-3", + "atalho_grafico.gif": "bar-chart", + "atalho_instituicao.gif": "bank", + "atalho_laboratorio.gif": "test-tube", + "atalho_lampada.gif": "lightbulb", + "atalho_lupa.gif": "search", + "atalho_martelo.gif": "hammer", + "atalho_mundo.gif": "earth", + "atalho_olho.gif": "user", + "atalho_pessoa.gif": "user", + "atalho_predio.gif": "building-4", + "atalho_roda_dentada.gif": "settings-4", + "atalho_smile_admirado.gif": "emotion", + "atalho_smile_feliz.gif": "emotion-happy", + "atalho_smile_triste.gif": "emotion-unhappy", + "atalho_tomada.gif": "plug", + AtencaoNPisca: "alert", + AtencaoPisca: "error-warning", + BotaoColapsar: "close-circle", + BotaoMin: "close-circle", + BotaoPersonalisar: "edit", + BotaoRestaurar: "add-circle", + Calendario: "calendar", + "CERT-Certidao": "file-paper-2", + "CERT-Visto": "checkbox-circle", + Comentario: "edit", + Completo: "checkbox-circle", + CriarNovo: "add-circle", + Descendente: "arrow-down-s", + DescendenteS: "arrow-down-s", + Documento: "file-text", + DocumentoCriar: "file-add", + EditarPerfil: "edit", + EnderecoEmail: "mail", + Erro: "error-warning", + EstadoSem1_off: "arrow-up-circle", + EstadoSem1: "checkbox-circle", + EstadoSem2: "arrow-down-circle", + EstadoSem3: "refresh", + EstadoSem4: "close-circle", + "facebook_32.png": "facebook-box", + Fazer: "indeterminate-circle", + "FEST-Selecionado": "radio-button", + "FEST-Selecionar": "checkbox-blank-circle", + "GENT-ContactoAguarda": "error-warning", + "GENT-ContactoOK": "checkbox-circle", + "GPAG-Documento-Impresso": "printer", + "GPAG-Documento": "file-text", + "GPAG-MB": "bank-card-2", + "In_Logo_Web4Print_CMYK_1in.jpg": "linkedin-box", + Informa: "information", + Inscrito: "indeterminate-circle", + "instagram40x40.png": "instagram", + "IPUP-Formula": "functions", + "IT-AlocAdm": "admin", + "IT-Email": "mail", + "IT-Fotos4x4": "layout-grid", + "IT-Fotos6x6": "grid", + JanelaFechar: "close-circle", + LegendaSemaforos: "question", + Limpar: "eraser", + ListaMais: "add-circle", + ListaMenos: "close-circle", + LOV: "arrow-left", + Lupa: "search", + MarcaTemporal: "checkbox-circle", + MarcaTemporalActivo: "play-circle", + MarcaTemporalOff: "checkbox-blank-circle", + Mensagem: "chat-4", + MensagemBBom: "emotion-laugh", + MensagemBom: "emotion-happy", + MensagemFilhos: "question-answer", + MensagemGrande: "sticky-note", + MensagemMau: "emotion-sad", + MensagemMMau: "emotion-unhappy", + MensagemNao: "thumb-down", + MensagemNeutro: "emotion-normal", + MensagemNova: "folder-unknown", + MensagemResponder: "edit", + MensagemSemFilhos: "chat-off", + MensagemSim: "thumb-up", + MensagemVelha: "folder", + MensagemVelhaV: "folder-open", + MenuContextoDetalhesMaisG: "zoom-in", + MenuContextoDetalhesMenosG: "zoom-out", + MenuContextoEditarG: "edit", + MenuContextoImagemPisoDescerOff: "arrow-right-down", + MenuContextoImagemPisoSubirOff: "arrow-right-up", + MenuContextoInicioG: "", + MoodleIcon: undefined, + NaoInscrito: "close-circle", + Novo: "star", + Obr: "asterisk", + PaginaWeb: "global", + Pasta: "folder-open", + PastaFechada: "folder", + Pessoa: "user", + Prior1: "arrow-up-double", + Prior2: "arrow-up-s", + Prior3: "subtract", + Prior4: "arrow-down-s", + Prior5: "arrow-down-double", + QuadradoMaisPeq: "add-circle", + QuadradoMenosPeq: "close-circle", + QuadradoPequenoMais: "add-circle", + QuadradoPequenoMenos: "close-circle", + SemAmarelo: "indeterminate-circle", + SemPermissoes: "error-warning", + SemRegistos: "forbid", + SemVerde: "checkbox-circle", + SemVermelho: "error-warning", + SetaDir: "arrow-right", + "SUMARIOS-Anexo": "attachment", + "SUMARIOS-AnexoOff": "attachment", + "SUMARIOS-VerSumario": "file-text", + "SUMARIOS-VerSumarioOff": "file-text", + Telef: "phone", + Visto: "checkbox-circle", + "youtube_32.png": "youtube", + ZipPeq: "file-zip", + }); + +export const FA_ICON_MAP: { readonly [key: string]: string } = Object.freeze({ + bars: "menu", + "ellipsis-v": "more-2", + "envelope-o": "mail", + fax: "printer", // TODO (toino): find better icon + laptop: "computer", + phone: "phone", + "plus-circle": "add-circle", + unlock: "login-circle", + envelope: "mail-unread", +}); + +export const BANNER_ICON_MAP: { readonly [key: string]: string } = + Object.freeze({ + alerta: "alert", + info: "information", + informa: "information", + }); + +export const BG_IMAGE_ICON_MAP: { readonly [key: string]: string } = + Object.freeze({ + ".acao.adicionar-elemento": "add-circle", + ".acao.adicionar": "add-circle", + ".acao.eliminar": "close-circle", + ".acao.limpar": "eraser", + ".EmAprovacao-28-14": "indeterminate-circle", + ".Impossibilidade-20-20": "error-warning", + ".Pausa-20-20": "pause", + ".terminar-sessao": "logout-circle", + }); + +export const EVENTS: readonly string[] = Object.freeze([ + "animationcancel", + "animationend", + "animationiteration", + "animationstart", + "afterscriptexecute", + "auxclick", + "beforescriptexecute", + "blur", + "click", + "compositionend", + "compositionstart", + "compositionupdate", + "contextmenu", + "copy", + "cut", + "dblclick", + "DOMActivate", + "DOMMouseScroll", + "error", + "focusin", + "focusout", + "focus", + "fullscreenchange", + "fullscreenerror", + "gesturechange", + "gestureend", + "gesturestart", + "gotpointercapture", + "keydown", + "keypress", + "keyup", + "lostpointercapture", + "mousedown", + "mouseenter", + "mouseleave", + "mousemove", + "mouseout", + "mouseover", + "mouseup", + "mousewheel", + "paste", + "pointercancel", + "pointerdown", + "pointerenter", + "pointerleave", + "pointermove", + "pointerout", + "pointerover", + "pointerup", + "scroll", + "select", + "touchcancel", + "touchend", + "touchmove", + "touchstart", + "transitioncancel", + "transitionend", + "transitionrun", + "transitionstart", + "webkitmouseforcechanged", + "webkitmouseforcedown", + "webkitmouseforceup", + "webkitmouseforcewillbegin", + "wheel", +]); diff --git a/content-scripts/modules/icons/index.js b/content-scripts/modules/icons/index.js deleted file mode 100644 index 393b2dd..0000000 --- a/content-scripts/modules/icons/index.js +++ /dev/null @@ -1,167 +0,0 @@ -import { - BANNER_ICON_MAP, - BG_IMAGE_ICON_MAP, - FA_ICON_MAP, - IMG_ICON_MAP, - EVENTS, -} from "./constants"; - -const addCSS = () => { - if (!document.querySelector('link[href$="remixicon.css"]')) - document.head.innerHTML += - ''; -}; - -const replaceImages = () => { - /** @type {(i: HTMLImageElement) => any} */ - const handleImage = (i) => { - const icon = IMG_ICON_MAP[i.src.substring(i.src.lastIndexOf("/") + 1)]; - - let span = null; - - if (i.nextElementSibling?.matches(".se-icon")) - span = i.nextElementSibling; - - if (icon === undefined) { - span?.remove(); - i.classList.remove("se-hidden-icon"); - return; - } - - i.classList.add("se-hidden-icon"); - - if (icon === "") { - span?.remove(); - return; - } - - if (!span) { - span = document.createElement("span"); - i.insertAdjacentElement("afterend", span); - copyEvents(i, span); - } - - let size = Math.max( - Math.round(Math.max(i.width, i.height) / 24) * 24, - 24, - ); - - copyAttrs(i, span); - span.classList.add(`ri-${icon}-line`, `ri-${icon}`); - span.style.fontSize = `${size}px`; - span.classList.add("se-icon"); - span.classList.remove("se-hidden-icon"); - }; - - new MutationObserver((ms) => - ms.forEach((m) => { - if (m.target instanceof HTMLImageElement) handleImage(m.target); - else if (m.addedNodes) - m.addedNodes.forEach((n) => { - if (n instanceof HTMLImageElement) handleImage(n); - else if (n instanceof HTMLElement) - n.querySelectorAll("img").forEach(handleImage); - }); - }), - ).observe(document, { - subtree: true, - childList: true, - attributeFilter: ["src"], - }); - - document.querySelectorAll("img").forEach(handleImage); -}; - -const copyAttrs = (el1, el2) => { - for (const attr of el1.attributes) - if (!attr.name.startsWith("on")) - try { - el2.setAttribute(attr.name, attr.value); - } catch (error) { - console.error(error); - } -}; - -const copyEvents = (el1, el2) => { - for (const event of EVENTS) - el2.addEventListener(event, (e) => { - el1.dispatchEvent(new e.constructor(e.type, e)); - e.stopPropagation(); - }); -}; - -/** - * @param {HTMLElement} el1 - * @param {HTMLElement} el2 - */ -const replaceWithKeepAttrs = (el1, el2) => { - for (const attr of el1.attributes) - try { - el2.setAttribute(attr.name, attr.value); - } catch (error) { - console.error(error); - } - - el2.append(...el1.children); - - el1.replaceWith(el2); -}; - -const replaceFA = () => { - document.querySelectorAll(".fa").forEach((i) => { - i.classList.remove("fa", "fa-fw"); - - i.classList.forEach((c) => { - if (!c.startsWith("fa-")) return; - - const icon = FA_ICON_MAP[c.replace("fa-", "")]; - if (icon) { - i.classList.remove(c); - i.classList.add(`ri-${icon}-line`, "se-icon"); - } - }); - }); -}; - -const replaceBgImages = () => { - Object.entries(BG_IMAGE_ICON_MAP).forEach(([selector, icon]) => { - document.querySelectorAll(selector).forEach((i) => { - if (icon === "") return i.classList.add("se-hidden-icon"); - - const span = document.createElement("span"); - span.classList.add("se-icon"); - span.classList.add(`ri-${icon}-line`); - - i.classList.add("se-remove-icon"); - - if (i.tagName == "img") replaceWithKeepAttrs(i, span); - else i.insertBefore(span, i.firstChild); - }); - }); -}; - -const replaceBanners = () => { - Object.entries(BANNER_ICON_MAP).forEach(([k, v]) => { - document.querySelectorAll(`.${k}`).forEach((i) => { - const span = document.createElement("span"); - span.innerHTML = i.innerHTML; - - const icon = document.createElement("span"); - icon.classList.add("se-icon", `ri-${v}-fill`); - icon.style.fontSize = "1.5em"; - - i.replaceChildren(icon, span); - }); - }); -}; - -// TODO (toino): handle this mess of a page -// https://sigarra.up.pt/feup/pt/FEST_GERAL.INQ_RAIDES_EDIT - -export const replaceIcons = () => { - addCSS(); - replaceImages(); - replaceFA(); - replaceBgImages(); - replaceBanners(); -}; diff --git a/content-scripts/modules/icons/index.tsx b/content-scripts/modules/icons/index.tsx new file mode 100644 index 0000000..c7b81ce --- /dev/null +++ b/content-scripts/modules/icons/index.tsx @@ -0,0 +1,209 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import jsx from "texsaur"; + +import { + BANNER_ICON_MAP, + BG_IMAGE_ICON_MAP, + FA_ICON_MAP, + IMG_ICON_MAP, + EVENTS, +} from "./constants"; + +/** + * Adds a link to the Remix Icon stylesheet if it's not already included in the document. + */ +const addCSS = (): void => { + if (!document.querySelector('link[href$="remixicon.css"]')) { + // TODO (thePeras): Why we don't shipped the css file with the extension already? + document.head.innerHTML += + ''; + } +}; + +/** + * Replaces elements with corresponding icons based on their src attributes. + */ +const replaceImages = (): void => { + /** + * Handles the replacement of an individual image element with its corresponding icon. + * @param {HTMLImageElement} i - The image element to process. + */ + const handleImage = (i: HTMLImageElement): void => { + const icon = IMG_ICON_MAP[i.src.substring(i.src.lastIndexOf("/") + 1)]; + + let span: HTMLSpanElement | null = null; + + if (i.nextElementSibling?.matches(".se-icon")) { + span = i.nextElementSibling as HTMLSpanElement; + } + + if (icon === undefined) { + span?.remove(); + i.classList.remove("se-hidden-icon"); + return; + } + + i.classList.add("se-hidden-icon"); + + if (icon === "") { + span?.remove(); + return; + } + + if (!span) { + span = document.createElement("span"); + i.insertAdjacentElement("afterend", span); + copyEvents(i, span); + } + + const size = Math.max( + Math.round(Math.max(i.width, i.height) / 24) * 24, + 24, + ); + + copyAttrs(i, span); + span.classList.add(`ri-${icon}-line`, `ri-${icon}`); + span.style.fontSize = `${size}px`; + span.classList.add("se-icon"); + span.classList.remove("se-hidden-icon"); + }; + + new MutationObserver((ms) => + ms.forEach((m) => { + if (m.target instanceof HTMLImageElement) { + handleImage(m.target); + } else if (m.addedNodes) { + m.addedNodes.forEach((n) => { + if (n instanceof HTMLImageElement) { + handleImage(n); + } else if (n instanceof HTMLElement) { + n.querySelectorAll("img").forEach(handleImage); + } + }); + } + }), + ).observe(document, { + subtree: true, + childList: true, + attributeFilter: ["src"], + }); + + document.querySelectorAll("img").forEach(handleImage); +}; + +/** + * Copies all attributes from one HTML element to another, excluding event attributes. + */ +const copyAttrs = (source: HTMLElement, target: HTMLElement): void => { + for (const attr of Array.from(source.attributes)) { + if (!attr.name.startsWith("on")) { + try { + target.setAttribute(attr.name, attr.value); + } catch (error) { + console.error(error); + } + } + } +}; + +/** + * Copies specified event listeners from one element to another. + */ +const copyEvents = (source: HTMLElement, target: HTMLElement): void => { + for (const event of EVENTS) { + target.addEventListener(event, (e: Event) => { + source.dispatchEvent(new Event(e.type, e)); + e.stopPropagation(); + }); + } +}; + +/** + * Replaces one HTML element with another while preserving attributes and child nodes. + * @param {HTMLElement} el1 - The element to be replaced. + * @param {HTMLElement} el2 - The element that will replace the first element. + */ +const replaceWithAttrs = (el1: HTMLElement, el2: HTMLElement): void => { + for (const attr of Array.from(el1.attributes)) { + try { + el2.setAttribute(attr.name, attr.value); + } catch (error) { + console.error(error); + } + } + + el2.append(...Array.from(el1.children)); + el1.replaceWith(el2); +}; + +/** + * Replaces Font Awesome icons in the document with their corresponding Remix Icons. + */ +const replaceFA = (): void => { + document.querySelectorAll(".fa").forEach((i) => { + i.classList.remove("fa", "fa-fw"); + + i.classList.forEach((c) => { + if (!c.startsWith("fa-")) return; + + const icon = FA_ICON_MAP[c.replace("fa-", "")]; + if (icon) { + i.classList.remove(c); + i.classList.add(`ri-${icon}-line`, "se-icon"); + } + }); + }); +}; + +/** + * Replaces background images in elements with corresponding icons based on a mapping. + */ +const replaceBgImages = (): void => { + Object.entries(BG_IMAGE_ICON_MAP).forEach(([selector, icon]) => { + document.querySelectorAll(selector).forEach((i) => { + if (icon === "") return i.classList.add("se-hidden-icon"); + + const span = ; + + i.classList.add("se-remove-icon"); + + if (i.tagName === "img") + replaceWithAttrs(i as HTMLElement, span as HTMLElement); + else i.insertBefore(span, i.firstChild); + }); + }); +}; + +/** + * Replaces banners in the document with corresponding icons based on a mapping. + */ +const replaceBanners = (): void => { + Object.entries(BANNER_ICON_MAP).forEach(([k, v]) => { + document.querySelectorAll(`.${k}`).forEach((i) => { + const span = {i.innerHTML}; + + // TODO (thePeras): Use , but you need to expand the Icon component to support fill and style + const icon = ( + + ); + i.replaceChildren(icon, span); + }); + }); +}; + +/** + * Initiates the icon replacement process for the document. + */ +export const replaceIcons = (): void => { + addCSS(); + replaceImages(); + replaceFA(); + replaceBgImages(); + replaceBanners(); +}; + +// TODO (toino): handle this mess of a page +// https://sigarra.up.pt/feup/pt/FEST_GERAL.INQ_RAIDES_EDIT diff --git a/content-scripts/modules/initialize.js b/content-scripts/modules/initialize.js index a99b138..4b6134a 100644 --- a/content-scripts/modules/initialize.js +++ b/content-scripts/modules/initialize.js @@ -1,21 +1,5 @@ -import throttle from "./utilities/throttle"; -import { getPath } from "./utilities/sigarra"; import { isDate, reverseDate } from "./utilities/date"; -// Resize Listener -export const addResizeListener = () => { - window.addEventListener( - "resize", - throttle(async () => { - /* - - Code here - - */ - }, 1000), - ); -}; - // Append override-functions.js to the page export const injectOverrideFunctions = () => { const script = document.createElement("script"); @@ -35,254 +19,6 @@ export const reverseDateDirection = () => { }); }; -export const currentAccountPage = () => { - if (getPath() != "gpag_ccorrente_geral.conta_corrente_view") return; - - const contaCorrente = document.getElementById( - "GPAG_CCORRENTE_GERAL_CONTA_CORRENTE_VIEW", - ); - if (contaCorrente) { - let tabs = contaCorrente.querySelectorAll(".tab"); - - // merge "Crédito" and "Débito" collumns and remove collumns - tabs.forEach((tab, tab_index) => { - let creditColumnIndex; - let columnsToRemove = []; - let rows = [...tab.querySelectorAll("thead > tr, tbody > tr")]; - if (rows.length == 0) return; - - let headerTitles = document.querySelectorAll( - "ul.ui-tabs-nav > li > a", - ); - headerTitles = [...headerTitles].map((title) => title.textContent); - let headerCells = rows[0].querySelectorAll("th"); - headerCells.forEach((th, index) => { - if (th.innerHTML == "Débito") { - th.innerHTML = "Valor"; - } else if (th.innerHTML == "Crédito") { - creditColumnIndex = index; - // Colspan - let colSpan = headerCells[0].colSpan; - if (colSpan > 1) creditColumnIndex += colSpan; - th.remove(); - } - - // Remove "Valor Pago" Column - if (th.innerHTML == "Valor Pago") { - columnsToRemove.push(index + headerCells[0].colSpan - 1); - th.remove(); - } - - // Remove "Valor em Falta" Column - if (th.innerHTML == "Valor em Falta") { - th.innerHTML = ""; - th.colSpan = 1; - columnsToRemove.push(index + headerCells[0].colSpan - 1); - } - - // Rename "Juros em Mora" Column - if (th.innerHTML == "Juros de Mora") { - th.innerHTML = "Juros"; - // Remove "Juros de Mora" Column in "Juros de mora Proprinas" tab - if (headerTitles[tab_index] == "Juros de mora Propinas") { - columnsToRemove.push( - index + headerCells[0].colSpan - 1, - ); - th.remove(); - } - } - - // Remove "Débito em Falta" Column - if (th.innerHTML == "Débito em Falta") { - columnsToRemove.push(index + headerCells[0].colSpan - 1); - th.remove(); - } - - // Remove "Valor em Falta" Column - if (th.innerHTML == "Documento") { - th.colSpan = 1; - columnsToRemove.push(index + headerCells[0].colSpan - 1); - } - - // Remove "Estado" Column - if (th.innerHTML == "Estado") { - columnsToRemove.push(index + headerCells[0].colSpan - 1); - th.remove(); - } - }); - - rows.shift(); - - rows.forEach((row) => { - let cells = [...row.querySelectorAll("td")]; - columnsToRemove.forEach((columnIndex) => { - if (!cells[0].classList.contains("credito")) { - cells[columnIndex].remove(); - } - }); - }); - - if (creditColumnIndex) { - rows.forEach((row, index) => { - let isGeralExtract = - headerTitles[tab_index] == "Extrato Geral"; - - let cells = [...row.querySelectorAll("td")]; - let debitCell = cells[creditColumnIndex - 1]; - - if (debitCell.innerHTML == " ") { - debitCell.innerHTML = ""; - debitCell.classList.add("n"); - if (isGeralExtract) { - debitCell.classList.add("positive"); - debitCell.innerHTML = "+"; - } - debitCell.innerHTML += - cells[creditColumnIndex].innerHTML; - } else { - if (isGeralExtract) { - debitCell.classList.add("negative"); - debitCell.innerHTML = "-" + debitCell.innerHTML; - } - } - cells[creditColumnIndex].remove(); - if (cells[0].classList.contains("credito")) { - //remove "Multibanco - SIBS" row - //TODO: adicionar data a "pago em" - - //change the last cell of the last row to the value of the last cell of the current row - let lastRowCells = - rows[index - 1].querySelectorAll("td"); - - let document_file = - cells[cells.length - 1].querySelector("a"); - if (document_file) { - lastRowCells[lastRowCells.length - 1].innerHTML = - ""; - lastRowCells[lastRowCells.length - 1].appendChild( - document_file, - ); - lastRowCells[ - lastRowCells.length - 1 - ].style.paddingRight = "0.6rem"; - } - row.remove(); - } - }); - } - }); - - // Change "Data" collumn position in "Extrato Geral" tab - const geralExtractTable = document.querySelector("#tab_extracto_geral"); - if (geralExtractTable) { - geralExtractTable.querySelectorAll("tr").forEach((row) => { - let cells = [ - ...row.querySelectorAll("td"), - ...row.querySelectorAll("th"), - ]; - // len = cells.length; - row.insertBefore(cells[1], cells[0]); - }); - } - - // Switch "Refência" action button to the right - if (tabs.length > 0) { - tabs[0].querySelectorAll("tbody > tr").forEach((row) => { - let cells = [ - ...row.querySelectorAll("td"), - ...row.querySelectorAll("th"), - ]; - let len = cells.length; - row.insertBefore(cells[len - 1], cells[len - 2]); - }); - } - - let statusProperties = { - Pago: { - class: "success", - text: "Pago", - }, - "Não pago mas prazo ainda não foi excedido": { - class: "pending", - text: "Pendente", - }, - Anulado: { - class: "cancelled", - text: "Anulado", - }, - "Prazo excedido": { - class: "danger", - text: "Excedido", - }, - }; - - // Improve the status badge - tabs.forEach((tab) => { - tab.querySelectorAll("tbody > tr").forEach((row) => { - let cells = [...row.querySelectorAll("td")]; - if (cells.length == 0) return; - - // Get title atriuibute from the first cell - const cellStatus = - cells[0].querySelector("img")?.getAttribute("title") ?? - null; - if (cellStatus == null) return; - - // Creating a new status badge - let statusDiv = document.createElement("div"); - statusDiv.innerHTML = statusProperties[cellStatus].text; - statusDiv.classList.add("badge"); - statusDiv.classList.add( - "badge-" + statusProperties[cellStatus].class, - ); - statusDiv.title = cellStatus; - - cells[0].innerHTML = statusDiv.outerHTML; - }); - }); - - // Remove "Movimentos" h2 - contaCorrente.previousElementSibling.remove(); - - // Create Balance and NIF cards - const saldo = document.querySelector( - ".formulario #span_saldo_total", - ).textContent; - const saldoCard = document.createElement("div"); - saldoCard.classList.add("card"); - const title = document.createElement("p"); - title.innerHTML = "Saldo"; - const saldoValue = document.createElement("h3"); - saldoValue.innerHTML = saldo + "€"; - saldoCard.appendChild(title); - saldoCard.appendChild(saldoValue); - - const nif = Array.from( - document.querySelectorAll(".formulario .formulario-legenda"), - ).filter((el) => el.innerHTML.includes("N.I.F."))[0].nextElementSibling - .innerHTML; - const nifCard = document.createElement("div"); - nifCard.classList.add("card"); - const nifTitle = document.createElement("p"); - nifTitle.innerHTML = "NIF"; - const nifValue = document.createElement("h3"); - nifValue.innerHTML = nif; - nifCard.appendChild(nifTitle); - nifCard.appendChild(nifValue); - - let accountDetails = document.createElement("div"); - accountDetails.style.display = "flex"; - accountDetails.style.gap = "1rem"; - accountDetails.style.marginBottom = "0.5rem"; - - accountDetails.appendChild(saldoCard); - accountDetails.appendChild(nifCard); - contaCorrente.insertBefore(accountDetails, contaCorrente.firstChild); - - return; - } -}; - // We are now doing this on our own table component (components/table.tsx), but this still works for regular tables that we didn't reimplement yet export const addSortTableActions = () => { document.querySelectorAll("th").forEach((th) => { diff --git a/content-scripts/modules/layout.ts b/content-scripts/modules/layout.ts index de21a86..96b7eda 100644 --- a/content-scripts/modules/layout.ts +++ b/content-scripts/modules/layout.ts @@ -1,5 +1,5 @@ import { AuthSession } from "../types"; -import Header from "../components/Header"; +import Header from "../components/Navbar/Header"; import { fetchSigarraPage } from "./utilities/pageUtils"; import Button from "../components/Button"; diff --git a/content-scripts/modules/options/addStyle.tsx b/content-scripts/modules/options/addStyle.tsx new file mode 100644 index 0000000..6d0fb1a --- /dev/null +++ b/content-scripts/modules/options/addStyle.tsx @@ -0,0 +1,14 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import jsx from "texsaur"; + +import removeElement from "../utilities/removeElement"; + +// Utility function to inject CSS into the page +export default function addStyles(id: string, css: string): void { + // If the element already exists, remove it + removeElement(id); + + const style = ; + const head = document.querySelector("head"); + if (head) head.appendChild(style); +} diff --git a/content-scripts/modules/options/addStyles.js b/content-scripts/modules/options/addStyles.js deleted file mode 100644 index 5c696e8..0000000 --- a/content-scripts/modules/options/addStyles.js +++ /dev/null @@ -1,13 +0,0 @@ -import removeElement from "../utilities/removeElement"; - -// Utility function to inject CSS into page -export default function addStyles(id, css) { - // First remove before adding - removeElement(id); - - const head = document.querySelector("head"); - const style = document.createElement("style"); - style.id = id; - style.textContent = `${css}`; - head.appendChild(style); -} diff --git a/content-scripts/modules/utilities/constructNewData.js b/content-scripts/modules/options/constructNewData.ts similarity index 60% rename from content-scripts/modules/utilities/constructNewData.js rename to content-scripts/modules/options/constructNewData.ts index 3aac2c6..face0d9 100644 --- a/content-scripts/modules/utilities/constructNewData.js +++ b/content-scripts/modules/options/constructNewData.ts @@ -1,12 +1,19 @@ // Utility function to create data for `injectAllChanges()` -export default function constructNewData(changes) { + +type Changes = { + [key: string]: chrome.storage.StorageChange; +}; + +export default function constructNewData(changes: Changes): { + [key: string]: unknown; +} { // Creates an array of objects from changes // The value of each object is the new value - const newValuesArray = Object.entries(changes).map((item) => { - const itemKey = item[0]; - const itemValue = item[1]?.newValue; - return { [itemKey]: itemValue }; - }); + const newValuesArray = Object.entries(changes).map( + ([itemKey, itemValue]) => { + return { [itemKey]: itemValue?.newValue }; + }, + ); // Recreate a hash map to pass to `injectAllChanges()` const newChangesData = Object.fromEntries( diff --git a/content-scripts/modules/options/index.js b/content-scripts/modules/options/index.ts similarity index 56% rename from content-scripts/modules/options/index.js rename to content-scripts/modules/options/index.ts index e0e2d01..b280d8d 100644 --- a/content-scripts/modules/options/index.js +++ b/content-scripts/modules/options/index.ts @@ -1,9 +1,15 @@ import { useNavBar, hideShortcuts, changeFont } from "./options"; // Array of user preferences, passed to `injectAllChanges` -export const userPreferences = ["navbar", "shortcuts", "autoLogin", "font"]; +export const userPreferences: string[] = [ + "navbar", + "shortcuts", + "autoLogin", + "font", +]; -export const injectAllChanges = (data) => { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const injectAllChanges = (data: any): void => { Promise.all([ hideShortcuts(data?.shortcuts), useNavBar(data?.navbar), diff --git a/content-scripts/modules/options/options.js b/content-scripts/modules/options/options.ts similarity index 65% rename from content-scripts/modules/options/options.js rename to content-scripts/modules/options/options.ts index 3c340e4..d2e71d9 100644 --- a/content-scripts/modules/options/options.js +++ b/content-scripts/modules/options/options.ts @@ -1,16 +1,17 @@ -import addStyles from "./addStyles"; +import addStyles from "./addStyle"; import removeElement from "../utilities/removeElement"; -export const hideShortcuts = async (shortcuts) => { +// TODO(thePeras & toni): These functions can be extracted singles files +export const hideShortcuts = async (shortcuts: string): Promise => { switch (shortcuts) { case "on": addStyles( "se-hide-shortcuts", ` - #caixa-atalhos{ - display: none - } - `, + #caixa-atalhos { + display: none; + } + `, ); break; @@ -20,16 +21,16 @@ export const hideShortcuts = async (shortcuts) => { } }; -export const changeFont = async (font) => { +export const changeFont = async (font: string): Promise => { switch (font) { case "on": addStyles( "se-change-font", ` - * { - font-family: Roboto, sans-serif; - } - `, + * { + font-family: Roboto, sans-serif; + } + `, ); break; @@ -39,16 +40,16 @@ export const changeFont = async (font) => { } }; -export const useNavBar = async (navbar) => { +export const useNavBar = async (navbar: string): Promise => { switch (navbar) { case "on": addStyles( "se-use-navbar", ` - #colunaprincipal, #rodape, #ferramentas{ + #colunaprincipal, #rodape, #ferramentas { display: none !important; } - `, + `, ); removeElement("#se-dont-use-navbar"); break; @@ -62,7 +63,7 @@ export const useNavBar = async (navbar) => { #colunaextra #caixa-campus { display: none !important; } - `, + `, ); removeElement("#se-use-navbar"); break; diff --git a/content-scripts/modules/utilities/storage.js b/content-scripts/modules/utilities/storage.js index 1f9e059..3370b3d 100644 --- a/content-scripts/modules/utilities/storage.js +++ b/content-scripts/modules/utilities/storage.js @@ -6,7 +6,6 @@ /* - Get storage with storage.local - k => "[key]" (String) -- Don't need to throttle */ export const getStorage = async (k) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -22,10 +21,6 @@ export const getStorage = async (k) => { /*-- - Set storage with storage.local - kv => {key: value} (Single key value pair) -- Throttle function to prevent hitting API limits -- The maximum number of set, remove, or clear operations = 120 - - 1 min = 60000 ms - - 60000 ms / 120 operations = 500 ms/operation --*/ export const setStorage = async (kv) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/content-scripts/modules/utilities/throttle.js b/content-scripts/modules/utilities/throttle.js deleted file mode 100644 index 6a1274b..0000000 --- a/content-scripts/modules/utilities/throttle.js +++ /dev/null @@ -1,29 +0,0 @@ -/*-- -- Simple utility throttle function with no return value -- Example usage: - const throttledFunc = throttle(function() { - // This function will only be called at most once every 1000 milliseconds - }, 1000) ---*/ -export default function throttle(func, limit) { - let lastFunc; - let lastRan; - return function () { - const args = arguments; - if (!lastRan) { - func.apply(this, args); - lastRan = Date.now(); - } else { - clearTimeout(lastFunc); - lastFunc = setTimeout( - function () { - if (Date.now() - lastRan >= limit) { - func.apply(this, args); - lastRan = Date.now(); - } - }, - limit - (Date.now() - lastRan), - ); - } - }; -} diff --git a/content-scripts/pages/current_account_page.js b/content-scripts/pages/current_account_page.js new file mode 100644 index 0000000..8098c41 --- /dev/null +++ b/content-scripts/pages/current_account_page.js @@ -0,0 +1,242 @@ +import { getPath } from "../modules/utilities/sigarra"; + +// TODO: Use our table, create a card component for the balance and NIF and use them +export const currentAccountPage = () => { + if (getPath() != "gpag_ccorrente_geral.conta_corrente_view") return; + + const contaCorrente = document.getElementById( + "GPAG_CCORRENTE_GERAL_CONTA_CORRENTE_VIEW", + ); + + if (!contaCorrente) return; + + let tabs = contaCorrente.querySelectorAll(".tab"); + + // merge "Crédito" and "Débito" collumns and remove collumns + tabs.forEach((tab, tab_index) => { + let creditColumnIndex; + let columnsToRemove = []; + let rows = [...tab.querySelectorAll("thead > tr, tbody > tr")]; + if (rows.length == 0) return; + + let headerTitles = document.querySelectorAll("ul.ui-tabs-nav > li > a"); + headerTitles = [...headerTitles].map((title) => title.textContent); + let headerCells = rows[0].querySelectorAll("th"); + headerCells.forEach((th, index) => { + if (th.innerHTML == "Débito") { + th.innerHTML = "Valor"; + } else if (th.innerHTML == "Crédito") { + creditColumnIndex = index; + // Colspan + let colSpan = headerCells[0].colSpan; + if (colSpan > 1) creditColumnIndex += colSpan; + th.remove(); + } + + // Remove "Valor Pago" Column + if (th.innerHTML == "Valor Pago") { + columnsToRemove.push(index + headerCells[0].colSpan - 1); + th.remove(); + } + + // Remove "Valor em Falta" Column + if (th.innerHTML == "Valor em Falta") { + th.innerHTML = ""; + th.colSpan = 1; + columnsToRemove.push(index + headerCells[0].colSpan - 1); + } + + // Rename "Juros em Mora" Column + if (th.innerHTML == "Juros de Mora") { + th.innerHTML = "Juros"; + // Remove "Juros de Mora" Column in "Juros de mora Proprinas" tab + if (headerTitles[tab_index] == "Juros de mora Propinas") { + columnsToRemove.push(index + headerCells[0].colSpan - 1); + th.remove(); + } + } + + // Remove "Débito em Falta" Column + if (th.innerHTML == "Débito em Falta") { + columnsToRemove.push(index + headerCells[0].colSpan - 1); + th.remove(); + } + + // Remove "Valor em Falta" Column + if (th.innerHTML == "Documento") { + th.colSpan = 1; + columnsToRemove.push(index + headerCells[0].colSpan - 1); + } + + // Remove "Estado" Column + if (th.innerHTML == "Estado") { + columnsToRemove.push(index + headerCells[0].colSpan - 1); + th.remove(); + } + }); + + rows.shift(); + + rows.forEach((row) => { + let cells = [...row.querySelectorAll("td")]; + columnsToRemove.forEach((columnIndex) => { + if (!cells[0].classList.contains("credito")) { + cells[columnIndex].remove(); + } + }); + }); + + if (creditColumnIndex) { + rows.forEach((row, index) => { + let isGeralExtract = headerTitles[tab_index] == "Extrato Geral"; + + let cells = [...row.querySelectorAll("td")]; + let debitCell = cells[creditColumnIndex - 1]; + + if (debitCell.innerHTML == " ") { + debitCell.innerHTML = ""; + debitCell.classList.add("n"); + if (isGeralExtract) { + debitCell.classList.add("positive"); + debitCell.innerHTML = "+"; + } + debitCell.innerHTML += cells[creditColumnIndex].innerHTML; + } else { + if (isGeralExtract) { + debitCell.classList.add("negative"); + debitCell.innerHTML = "-" + debitCell.innerHTML; + } + } + cells[creditColumnIndex].remove(); + if (cells[0].classList.contains("credito")) { + //remove "Multibanco - SIBS" row + //TODO: adicionar data a "pago em" + + //change the last cell of the last row to the value of the last cell of the current row + let lastRowCells = rows[index - 1].querySelectorAll("td"); + + let document_file = + cells[cells.length - 1].querySelector("a"); + if (document_file) { + lastRowCells[lastRowCells.length - 1].innerHTML = ""; + lastRowCells[lastRowCells.length - 1].appendChild( + document_file, + ); + lastRowCells[ + lastRowCells.length - 1 + ].style.paddingRight = "0.6rem"; + } + row.remove(); + } + }); + } + }); + + // Change "Data" collumn position in "Extrato Geral" tab + const geralExtractTable = document.querySelector("#tab_extracto_geral"); + if (geralExtractTable) { + geralExtractTable.querySelectorAll("tr").forEach((row) => { + let cells = [ + ...row.querySelectorAll("td"), + ...row.querySelectorAll("th"), + ]; + // len = cells.length; + row.insertBefore(cells[1], cells[0]); + }); + } + + // Switch "Refência" action button to the right + if (tabs.length > 0) { + tabs[0].querySelectorAll("tbody > tr").forEach((row) => { + let cells = [ + ...row.querySelectorAll("td"), + ...row.querySelectorAll("th"), + ]; + let len = cells.length; + row.insertBefore(cells[len - 1], cells[len - 2]); + }); + } + + let statusProperties = { + Pago: { + class: "success", + text: "Pago", + }, + "Não pago mas prazo ainda não foi excedido": { + class: "pending", + text: "Pendente", + }, + Anulado: { + class: "cancelled", + text: "Anulado", + }, + "Prazo excedido": { + class: "danger", + text: "Excedido", + }, + }; + + // Improve the status badge + tabs.forEach((tab) => { + tab.querySelectorAll("tbody > tr").forEach((row) => { + let cells = [...row.querySelectorAll("td")]; + if (cells.length == 0) return; + + // Get title atriuibute from the first cell + const cellStatus = + cells[0].querySelector("img")?.getAttribute("title") ?? null; + if (cellStatus == null) return; + + // Creating a new status badge + let statusDiv = document.createElement("div"); + statusDiv.innerHTML = statusProperties[cellStatus].text; + statusDiv.classList.add("badge"); + statusDiv.classList.add( + "badge-" + statusProperties[cellStatus].class, + ); + statusDiv.title = cellStatus; + + cells[0].innerHTML = statusDiv.outerHTML; + }); + }); + + // Remove "Movimentos" h2 + contaCorrente.previousElementSibling.remove(); + + // Create Balance and NIF cards + const saldo = document.querySelector( + ".formulario #span_saldo_total", + ).textContent; + const saldoCard = document.createElement("div"); + saldoCard.classList.add("card"); + const title = document.createElement("p"); + title.innerHTML = "Saldo"; + const saldoValue = document.createElement("h3"); + saldoValue.innerHTML = saldo + "€"; + saldoCard.appendChild(title); + saldoCard.appendChild(saldoValue); + + const nif = Array.from( + document.querySelectorAll(".formulario .formulario-legenda"), + ).filter((el) => el.innerHTML.includes("N.I.F."))[0].nextElementSibling + .innerHTML; + const nifCard = document.createElement("div"); + nifCard.classList.add("card"); + const nifTitle = document.createElement("p"); + nifTitle.innerHTML = "NIF"; + const nifValue = document.createElement("h3"); + nifValue.innerHTML = nif; + nifCard.appendChild(nifTitle); + nifCard.appendChild(nifValue); + + let accountDetails = document.createElement("div"); + accountDetails.style.display = "flex"; + accountDetails.style.gap = "1rem"; + accountDetails.style.marginBottom = "0.5rem"; + + accountDetails.appendChild(saldoCard); + accountDetails.appendChild(nifCard); + contaCorrente.insertBefore(accountDetails, contaCorrente.firstChild); + + return; +}; diff --git a/popup/components/layout/Main.js b/popup/components/layout/Main.js deleted file mode 100644 index 63527e6..0000000 --- a/popup/components/layout/Main.js +++ /dev/null @@ -1,9 +0,0 @@ -import Layout from "../sections/Layout"; - -const Main = () => ( -
- -
-); - -export default Main; diff --git a/popup/components/layout/Section.js b/popup/components/layout/Section.js new file mode 100644 index 0000000..78d4883 --- /dev/null +++ b/popup/components/layout/Section.js @@ -0,0 +1,19 @@ +const Section = ({ title, children }) => ( +
+ +
+
+
+
{children}
+
+
+
+
+); + +export default Section; diff --git a/popup/components/sections/Features.js b/popup/components/sections/Features.js new file mode 100644 index 0000000..1524812 --- /dev/null +++ b/popup/components/sections/Features.js @@ -0,0 +1,10 @@ +import Section from "../layout/Section"; +import SwitchControl from "../controls/SwitchControl"; + +const Layout = () => ( +
+ +
+); + +export default Layout; diff --git a/popup/components/sections/Layout.js b/popup/components/sections/Layout.js index 96a4fae..94eb944 100644 --- a/popup/components/sections/Layout.js +++ b/popup/components/sections/Layout.js @@ -1,17 +1,12 @@ -import LayoutContent from "./LayoutContent"; +import Section from "../layout/Section"; +import SwitchControl from "../controls/SwitchControl"; const Layout = () => ( -
- -
- -
-
+
+ + + +
); export default Layout; diff --git a/popup/components/sections/LayoutContent.js b/popup/components/sections/LayoutContent.js deleted file mode 100644 index 773aae4..0000000 --- a/popup/components/sections/LayoutContent.js +++ /dev/null @@ -1,25 +0,0 @@ -import SwitchControl from "../controls/SwitchControl"; - -const LayoutContent = () => ( -
-
-
- - - - -
-
-
-); - -export default LayoutContent; diff --git a/popup/pages/index.js b/popup/pages/index.js index 1dce5d2..5baeafa 100644 --- a/popup/pages/index.js +++ b/popup/pages/index.js @@ -1,12 +1,17 @@ import Container from "../components/layout/Container"; import Footer from "../components/layout/Footer"; import Header from "../components/layout/Header"; -import Main from "../components/layout/Main"; + +import Layout from "../components/sections/Layout"; +import Features from "../components/sections/Features"; const IndexPage = () => (
-
+
+ + +