diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..feb3b92 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,58 @@ +version: 2.1 + +commands: + install_dependencies: + description: "Installs dependencies and uses CircleCI's cache" + steps: + - checkout + - restore_cache: + keys: + - dependencies-{{ checksum "yarn.lock" }} + - dependencies- + - run: + command: | + yarn install + - save_cache: + paths: + - node_modules + key: dependencies-{{ checksum "yarn.lock" }} + +jobs: + style: + docker: + - image: node:latest + steps: + - install_dependencies + - run: + name: prettier + command: | + yarn prettier --check + - run: + name: lint + command: | + yarn lint + + dashboard: + docker: + - image: node:latest + steps: + - install_dependencies + - run: + name: typecheck + command: | + yarn typecheck + - run: + name: test + command: | + yarn test + - run: + name: coverage + command: | + yarn coverage + +workflows: + version: 2 + dashboard: + jobs: + - style + - dashboard diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..d46a4a1 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,9 @@ +module.exports = { + extends: ["eslint:recommended", "plugin:react-hooks/recommended"], + rules: { + maxClassesPerFile: 0, + maxLineLength: 0, + memberOrdering: 0, + variableName: 0, + }, +}; diff --git a/.gitignore b/.gitignore index 7d9df53..7539759 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + + diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..363e53c --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,21 @@ +module.exports = { + bracketSpacing: true, + printWidth: 80, + parser: "typescript", + trailingComma: "all", + arrowParens: "always", + overrides: [ + { + files: "README.md", + options: { + parser: "markdown", + }, + }, + { + files: "*.json", + options: { + parser: "json", + } + } + ], +}; \ No newline at end of file diff --git a/README.md b/README.md index cb73d45..ce8afac 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # Dashboard +[![CircleCI][shield-circleci]][circleci] +[![Docker Cloud Build Status][shield-docker]][docker] +[![Docker Image Size (latest)][shield-docker-image]][docker] +[![codecov][shield-codecov]][codecov] +[![Dependencies][shield-deps]][repo] +[![GitHub license][shield-license]][license] + ![Alt text](/screenshot.png?raw=true "screenshot") Dashboard is just that - a dashboard. It's inspired by [SUI](https://github.com/jeroenpardon/sui) and has all the same features as SUI, such as simple customization through JSON-files and a handy search bar to search the internet more efficiently. @@ -199,3 +206,15 @@ In order for the imprint-modal to show up, make sure your `imprint.json` resembl ``` > :exclamation: I haven't quite tested this. I'm not a lawyer and I'm not responsible if you're sued for using this incorrectly. + +[docker]: https://hub.docker.com/r/phntxx/dashboard +[codecov]: https://codecov.io/gh/phntxx/dashboard +[repo]: https://github.com/phntxx/dashboard +[license]: https://github.com/phntxx/dashboard/LICENSE +[circleci]: https://circleci.com/gh/phntxx/dashboard +[shield-docker]: https://img.shields.io/docker/cloud/build/phntxx/dashboard +[shield-docker-image]: https://img.shields.io/docker/image-size/phntxx/dashboard/latest +[shield-circleci]: https://circleci.com/gh/phntxx/dashboard.svg?style=shield +[shield-codecov]: https://codecov.io/gh/phntxx/dashboard/branch/master/graph/badge.svg +[shield-license]: https://img.shields.io/github/license/phntxx/dashboard.svg +[shield-deps]: https://img.shields.io/david/phntxx/dashboard diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..d4acc21 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,13 @@ +coverage: + status: + project: + default: off + dashboard: + target: 80% + flags: dashboard + +flags: + dashboard: + paths: + - src/components/ + - src/lib/ diff --git a/data/greeter.json b/data/greeter.json index 487bd37..e01f136 100644 --- a/data/greeter.json +++ b/data/greeter.json @@ -42,7 +42,7 @@ { "greeting": "Good evening!", "start": 18, - "end": 0 + "end": 24 } ], "dateformat": "%wd, %m %d%e %y" diff --git a/package.json b/package.json index 151d6bd..9f5afc2 100644 --- a/package.json +++ b/package.json @@ -8,14 +8,13 @@ ], "private": false, "dependencies": { - "@testing-library/jest-dom": "^5.11.10", - "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^13.0.6", - "@types/jest": "^26.0.22", "@types/node": "^14.14.37", "@types/react-dom": "^17.0.3", "@types/react-select": "^4.0.13", "@types/styled-components": "^5.1.9", + "browserslist": "^4.16.6", + "http-server": "^0.12.3", + "npm-run-all": "^4.1.5", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "^4.0.3", @@ -23,11 +22,28 @@ "styled-components": "^5.2.1", "typescript": "^4.2.3" }, + "devDependencies": { + "@testing-library/jest-dom": "^5.11.10", + "@testing-library/react": "^11.2.7", + "@testing-library/user-event": "^13.0.6", + "@types/jest": "^26.0.22", + "codecov": "^3.8.2", + "eslint": "^7.28.0", + "eslint-plugin-react-hooks": "^4.2.0", + "prettier": "^2.3.1" + }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", + "coverage": "codecov -f coverage/*.json -F dashboard", "test": "react-scripts test", - "eject": "react-scripts eject" + "typecheck": "tsc --noEmit", + "eject": "react-scripts eject", + "lint": "eslint --config .eslintrc.js", + "prettier": "prettier --config .prettierrc.js '{data,src}/**/*.{json,ts,tsx}'", + "http-server:data": "http-server ./ -c-1", + "http-server:app": "http-server ./build --proxy http://localhost:8080 --port 3000", + "serve:production": "npm-run-all --parallel http-server:data http-server:app" }, "eslintConfig": { "extends": "react-app" diff --git a/src/app.tsx b/src/app.tsx index 0a5293b..da2f2d5 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -3,7 +3,7 @@ import { createGlobalStyle } from "styled-components"; import SearchBar from "./components/searchBar"; import Greeter from "./components/greeter"; import AppList from "./components/appList"; -import BookmarkList from "./components/bookmarkList"; +import BookmarkList from "./components/bookmarks"; import Settings from "./components/settings"; import Imprint from "./components/imprint"; @@ -29,20 +29,27 @@ const GlobalStyle = createGlobalStyle` * Renders the entire app by calling individual components */ const App = () => { - - const { appData, bookmarkData, searchProviderData, themeData, imprintData, greeterData } = useFetcher(); + const { + appData, + bookmarkData, + searchProviderData, + themeData, + imprintData, + greeterData, + } = useFetcher(); return ( <>
- {!themeData.error && !searchProviderData.error && ( - - )} + {!themeData.error || + (!searchProviderData.error && ( + + ))} {!appData.error && ( diff --git a/src/components/app.tsx b/src/components/app.tsx index aabd910..9c48be3 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -1,12 +1,17 @@ -import React, { useEffect } from "react"; import Icon from "./icon"; import styled from "styled-components"; import selectedTheme from "../lib/theme"; -const AppContainer = styled.div` +const AppContainer = styled.a` display: flex; - flex: auto 25%; + flex: 1 0 auto; padding: 1rem; + color: ${selectedTheme.mainColor}; + font-weight: 500; + text-transform: uppercase; + margin: 0; + text-decoration: none; + font-size: 1rem; `; const IconContainer = styled.div` @@ -21,16 +26,8 @@ const DetailsContainer = styled.div` flex-direction: column; `; -const AppLink = styled.a` - flex: 1 0 auto; - color: ${selectedTheme.mainColor}; - font-weight: 500; - text-transform: uppercase; - margin: 0; - text-decoration: none; - font-size: 1rem; - - &:hover { +const AppName = styled.div` + a:hover & { text-decoration: underline; } `; @@ -53,26 +50,29 @@ export interface IAppProps { /** * Renders a single app shortcut - * @param {IAppProps} props - The props of the given app + * @param {IAppProps} props the props of the given app + * @returns {React.ReactNode} the child node for the given app */ -export const App = ({ name, icon, url, displayURL, newTab }: IAppProps) => { - - useEffect(() => { console.log(newTab) }, [newTab]) +const App = ({ name, icon, url, displayURL, newTab }: IAppProps) => { + const linkAttrs = + newTab !== undefined && newTab + ? { + target: "_blank", + rel: "noopener noreferrer", + } + : {}; return ( - + - - { - (newTab !== undefined && newTab) ? - {name} : {name} - } - + {name} {displayURL} ); -} +}; + +export default App; diff --git a/src/components/appCategory.tsx b/src/components/appCategory.tsx index 83d0fc3..8e86629 100644 --- a/src/components/appCategory.tsx +++ b/src/components/appCategory.tsx @@ -1,6 +1,5 @@ -import React from "react"; import styled from "styled-components"; -import { App, IAppProps } from "./app"; +import App, { IAppProps } from "./app"; import { ItemList, Item, SubHeadline } from "./elements"; const CategoryHeadline = styled(SubHeadline)` @@ -18,9 +17,10 @@ export interface IAppCategoryProps { /** * Renders one app category - * @param {IAppCategoryProps} props - The props of the given category + * @param {IAppCategoryProps} props props of the given category + * @returns {React.ReactNode} the app category node */ -export const AppCategory = ({ name, items }: IAppCategoryProps) => ( +const AppCategory = ({ name, items }: IAppCategoryProps) => ( {name && {name}} @@ -38,3 +38,5 @@ export const AppCategory = ({ name, items }: IAppCategoryProps) => ( ); + +export default AppCategory; diff --git a/src/components/appList.tsx b/src/components/appList.tsx index 3781438..6319acf 100644 --- a/src/components/appList.tsx +++ b/src/components/appList.tsx @@ -1,4 +1,4 @@ -import { AppCategory, IAppCategoryProps } from "./appCategory"; +import AppCategory, { IAppCategoryProps } from "./appCategory"; import { IAppProps } from "./app"; import { Headline, ListContainer } from "./elements"; @@ -10,7 +10,8 @@ export interface IAppListProps { /** * Renders one list containing all app categories and uncategorized apps - * @param {IAppListProps} props - The props of the given list of apps + * @param {IAppListProps} props props of the given list of apps + * @returns {React.ReactNode} the app list component */ const AppList = ({ categories, apps }: IAppListProps) => ( @@ -20,10 +21,7 @@ const AppList = ({ categories, apps }: IAppListProps) => ( ))} {apps && ( - + )} ); diff --git a/src/components/bookmarkList.tsx b/src/components/bookmarkList.tsx deleted file mode 100644 index 7f07852..0000000 --- a/src/components/bookmarkList.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; -import { Headline, ListContainer, ItemList } from "./elements"; -import { BookmarkGroup, IBookmarkGroupProps } from "./bookmarkGroup"; - -interface IBookmarkListProps { - groups: Array; -} - -/** - * Renders a given list of categorized bookmarks - * @param {IBookmarkListProps} props - The props of the given bookmark list - */ -const BookmarkList = ({ groups }: IBookmarkListProps) => ( - - Bookmarks - - {groups.map(({ name, items }, idx) => ( - - ))} - - -); - -export default BookmarkList; diff --git a/src/components/bookmarkGroup.tsx b/src/components/bookmarks.tsx similarity index 53% rename from src/components/bookmarkGroup.tsx rename to src/components/bookmarks.tsx index 1a44d27..5893cb1 100644 --- a/src/components/bookmarkGroup.tsx +++ b/src/components/bookmarks.tsx @@ -1,6 +1,11 @@ -import React from "react"; import styled from "styled-components"; -import { Item, SubHeadline } from "./elements"; +import { + Headline, + Item, + ItemList, + ListContainer, + SubHeadline, +} from "./elements"; import selectedTheme from "../lib/theme"; const GroupContainer = styled.div` @@ -32,9 +37,14 @@ export interface IBookmarkGroupProps { items: Array; } +export interface IBookmarkListProps { + groups: Array; +} + /** * Renders a given bookmark group - * @param {IBookmarkGroupProps} props - The given props of the bookmark group + * @param {IBookmarkGroupProps} props given props of the bookmark group + * @returns {React.ReactNode} the bookmark group component */ export const BookmarkGroup = ({ name, items }: IBookmarkGroupProps) => ( @@ -48,3 +58,21 @@ export const BookmarkGroup = ({ name, items }: IBookmarkGroupProps) => ( ); + +/** + * Renders a given list of categorized bookmarks + * @param {IBookmarkListProps} props props of the given bookmark list + * @returns {React.ReactNode} the bookmark list component + */ +const BookmarkList = ({ groups }: IBookmarkListProps) => ( + + Bookmarks + + {groups.map(({ name, items }, idx) => ( + + ))} + + +); + +export default BookmarkList; diff --git a/src/components/elements.tsx b/src/components/elements.tsx index 48d81de..5ea4667 100644 --- a/src/components/elements.tsx +++ b/src/components/elements.tsx @@ -1,7 +1,5 @@ -import React from "react"; import styled from "styled-components"; import selectedTheme from "../lib/theme"; -import Icon from "./icon"; export const ListContainer = styled.div` padding: 2rem 0; @@ -56,29 +54,3 @@ export const Button = styled.button` cursor: pointer; } `; - -const StyledButton = styled.button` - float: right; - border: none; - padding: 0; - background: none; - - &:hover { - cursor: pointer; - } -`; - -interface IIconButtonProps { - icon: string; - onClick: (e: React.FormEvent) => void; -} - -/** - * Renders a button with an icon - * @param {IIconProps} props - The props of the given IconButton - */ -export const IconButton = ({ icon, onClick }: IIconButtonProps) => ( - - - -); diff --git a/src/components/greeter.tsx b/src/components/greeter.tsx index b91185d..25170d8 100644 --- a/src/components/greeter.tsx +++ b/src/components/greeter.tsx @@ -20,6 +20,10 @@ const DateText = styled.h3` color: ${selectedTheme.accentColor}; `; +export interface IGreeterComponentProps { + data: IGreeterProps; +} + export interface IGreeterProps { months: Array; days: Array; @@ -33,50 +37,48 @@ interface IGreetingProps { end: number; } -interface IGreeterComponentProps { - data: IGreeterProps; -} - /** - * - * @param a the number that's supposed to be checked - * @param b the minimum - * @param c the maximum + * Checks if a number is between two numbers + * @param {number} a number that's supposed to be checked + * @param {number} b minimum + * @param {number} c maximum */ -const isBetween = (a: number, b: number, c: number): boolean => (a > b && a < c) +export const isBetween = (a: number, b: number, c: number): boolean => + a >= b && a <= c; /** * Returns a greeting based on the current time - * @returns {string} - A greeting + * @param {Array} greetings a list of greetings with start and end date + * @returns {string} a greeting */ -const getGreeting = (greetings: Array): string => { - - let hours = Math.floor(new Date().getHours()) +export const getGreeting = (greetings: Array): string => { + let hours = Math.floor(new Date().getHours()); let result = ""; - greetings.forEach(greeting => { - if (isBetween(hours, greeting.start, greeting.end)) result = greeting.greeting; - }) + greetings.forEach((greeting) => { + if (isBetween(hours, greeting.start, greeting.end)) + result = greeting.greeting; + }); return result; }; /** * Returns the appropriate extension for a number (eg. 'rd' for '3' to make '3rd') - * @param {number} day - The number of a day within a month - * @returns {string} - The extension for that number + * @param {number} day number of a day within a month + * @returns {string} extension for that number */ -const getExtension = (day: number) => { +export const getExtension = (day: number) => { let extension = ""; - if ((day > 4 && day <= 20) || (day > 20 && day % 10 >= 4)) { - extension = "th"; - } else if (day % 10 === 1) { + if (day % 10 === 1) { extension = "st"; } else if (day % 10 === 2) { extension = "nd"; } else if (day % 10 === 3) { extension = "rd"; + } else if (isBetween(day, 4, 20) || (day > 20 && day % 10 >= 4)) { + extension = "th"; } return extension; @@ -84,10 +86,14 @@ const getExtension = (day: number) => { /** * Generates the current date - * @param {string} format - The format of the date string - * @returns {string} - The current date as a string + * @param {string} format format of the date string + * @returns {string} current date as a string */ -const getDateString = (weekdays: Array, months: Array, format: string) => { +export const getDateString = ( + weekdays: Array, + months: Array, + format: string, +) => { let currentDate = new Date(); let weekday = weekdays[currentDate.getUTCDay()]; @@ -96,15 +102,24 @@ const getDateString = (weekdays: Array, months: Array, format: s let extension = getExtension(day); let year = currentDate.getFullYear(); - return format.replace("%wd", weekday).replace("%d", day.toString()).replace("%e", extension).replace("%m", month).replace("%y", year.toString()); + return format + .replace("%wd", weekday) + .replace("%d", day.toString()) + .replace("%e", extension) + .replace("%m", month) + .replace("%y", year.toString()); }; /** * Renders the Greeter + * @param {IGreeterComponentProps} data required greeter data + * @returns {React.ReactNode} the greeter */ const Greeter = ({ data }: IGreeterComponentProps) => ( - {getDateString(data.days, data.months, data.dateformat)} + + {getDateString(data.days, data.months, data.dateformat)} + {getGreeting(data.greetings)} ); diff --git a/src/components/icon.tsx b/src/components/icon.tsx index 8c98bc6..a3fb907 100644 --- a/src/components/icon.tsx +++ b/src/components/icon.tsx @@ -7,32 +7,62 @@ interface IIconProps { size?: string; } +interface IIconButtonProps { + testid?: string; + icon: string; + onClick: (e: React.FormEvent) => void; +} + +const StyledButton = styled.button` + float: right; + border: none; + padding: 0; + background: none; + + &:hover { + cursor: pointer; + } +`; + +const IconContainer = styled.i` + font-family: "Material Icons"; + font-weight: normal; + font-style: normal; + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + -moz-osx-font-smoothing: grayscale; + font-feature-settings: "liga"; + font-size: ${(props) => props.about}; + color: ${(props) => props.color}; +`; + /** * Renders an Icon - * @param {IIconProps} props - The props needed for the given icon + * @param {IIconProps} props props needed for the given icon + * @returns {React.ReactNode} the icon node */ -export const Icon = ({ name, size }: IIconProps) => { +export const Icon = ({ name, size }: IIconProps) => ( + + {name} + +); - let IconContainer = styled.i` - font-family: "Material Icons"; - font-weight: normal; - font-style: normal; - font-size: ${size ? size : "24px"}; - color: ${selectedTheme.mainColor}; - display: inline-block; - line-height: 1; - text-transform: none; - letter-spacing: normal; - word-wrap: normal; - white-space: nowrap; - direction: ltr; - -webkit-font-smoothing: antialiased; - text-rendering: optimizeLegibility; - -moz-osx-font-smoothing: grayscale; - font-feature-settings: "liga"; - `; - - return {name}; -}; +/** + * Renders a button with an icon + * @param {IIconProps} props - The props of the given IconButton + * @returns {React.ReactNode} the icon button node + */ +export const IconButton = ({ testid, icon, onClick }: IIconButtonProps) => ( + + + +); export default Icon; diff --git a/src/components/imprint.tsx b/src/components/imprint.tsx index 8567d86..ad2f828 100644 --- a/src/components/imprint.tsx +++ b/src/components/imprint.tsx @@ -2,12 +2,7 @@ import React from "react"; import Modal from "./modal"; import styled from "styled-components"; import selectedTheme from "../lib/theme"; -import { - ListContainer, - ItemList, - Headline, - SubHeadline, -} from "./elements"; +import { ListContainer, ItemList, Headline, SubHeadline } from "./elements"; const ModalSubHeadline = styled(SubHeadline)` display: block; @@ -37,11 +32,6 @@ const ItemContainer = styled.div` padding: 1rem 0; `; -interface IImprintFieldProps { - text: string; - link: string; -} - export interface IImprintProps { name: IImprintFieldProps; address: IImprintFieldProps; @@ -51,25 +41,32 @@ export interface IImprintProps { text: string; } +export interface IImprintComponentProps { + imprint: IImprintProps; +} + interface IImprintFieldComponentProps { field: IImprintFieldProps; } -interface IImprintComponentProps { - imprint: IImprintProps; +interface IImprintFieldProps { + text: string; + link: string; } /** * Renders an imprint field - * @param {IImprintFieldComponentProps} props - The data for the field + * @param {IImprintFieldComponentProps} props data for the field + * @returns {React.ReactNode} the imprint field component */ -const ImprintField = ({ field }: IImprintFieldComponentProps) => ( +export const ImprintField = ({ field }: IImprintFieldComponentProps) => ( {field.text} ); /** * Renders the imprint component - * @param {IImprintProps} props - The contents of the imprint + * @param {IImprintProps} props contents of the imprint + * @returns {React.ReactNode} the imprint node */ const Imprint = ({ imprint }: IImprintComponentProps) => ( <> @@ -85,15 +82,17 @@ const Imprint = ({ imprint }: IImprintComponentProps) => ( condition={!window.location.href.endsWith("#imprint")} onClose={() => { if (window.location.href.endsWith("#imprint")) { - let location = window.location.href.replace("#imprint", ""); - window.location.href = location; + window.location.href = window.location.href.replace( + "#imprint", + "", + ); } }} >
Information in accordance with section 5 TMG - + <> {imprint.name && } {imprint.address && } @@ -103,9 +102,7 @@ const Imprint = ({ imprint }: IImprintComponentProps) => (
- - Imprint - + Imprint {imprint.text && {imprint.text}}
diff --git a/src/components/modal.tsx b/src/components/modal.tsx index aede217..8b18bea 100644 --- a/src/components/modal.tsx +++ b/src/components/modal.tsx @@ -2,7 +2,8 @@ import React, { useState } from "react"; import styled from "styled-components"; import selectedTheme from "../lib/theme"; -import { Headline, IconButton } from "./elements"; +import { Headline } from "./elements"; +import { IconButton } from "./icon"; const ModalContainer = styled.div` position: absolute; @@ -37,7 +38,7 @@ const TitleContainer = styled.div` justify-content: space-between; `; -interface IModalProps { +export interface IModalProps { element: string; icon?: string; text?: string; @@ -49,9 +50,18 @@ interface IModalProps { /** * Renders a modal with button to hide and un-hide - * @param {IModalProps} props - The needed props for the modal + * @param {IModalProps} props needed props for the modal + * @returns {React.ReactNode} the modal component */ -const Modal = ({ element, icon, text, condition, title, onClose, children }: IModalProps) => { +const Modal = ({ + element, + icon, + text, + condition, + title, + onClose, + children, +}: IModalProps) => { const [modalHidden, setModalHidden] = useState(condition ?? true); const closeModal = () => { @@ -62,17 +72,27 @@ const Modal = ({ element, icon, text, condition, title, onClose, children }: IMo return ( <> {element === "icon" && ( - closeModal()} /> + closeModal()} + /> )} {element === "text" && ( - closeModal()}>{text} + closeModal()}> + {text} + )} diff --git a/src/components/searchBar.tsx b/src/components/searchBar.tsx index 2df0162..d1931c5 100644 --- a/src/components/searchBar.tsx +++ b/src/components/searchBar.tsx @@ -16,18 +16,17 @@ const Search = styled.form` const SearchInput = styled.input` width: 100%; + margin: 0px; font-size: 1rem; border: none; border-bottom: 1px solid ${selectedTheme.accentColor}; + border-radius: 0; background: none; - border-radius: 0; color: ${selectedTheme.mainColor}; - margin: 0px; - :focus { outline: none; } @@ -53,9 +52,33 @@ interface ISearchBarProps { search: ISearchProps; } +export const handleQueryWithProvider = ( + search: ISearchProps, + query: string, +) => { + let queryArray: Array = query.split(" "); + let prefix: string = queryArray[0]; + + queryArray.shift(); + + let searchQuery: string = queryArray.join(" "); + + let providerFound: boolean = false; + if (search.providers) { + search.providers.forEach((provider: ISearchProviderProps) => { + if (provider.prefix === prefix) { + providerFound = true; + window.location.href = provider.url + searchQuery; + } + }); + } + + if (!providerFound) window.location.href = search.defaultProvider + query; +}; + /** * Renders a search bar - * @param {ISearchBarProps} search - The search providers for the search bar to use + * @param {ISearchBarProps} search - The search providers for the search bar to use */ const SearchBar = ({ search }: ISearchBarProps) => { let [input, setInput] = useState(""); @@ -67,7 +90,7 @@ const SearchBar = ({ search }: ISearchBarProps) => { var query: string = input || ""; if (query.split(" ")[0].includes("/")) { - handleQueryWithProvider(query); + handleQueryWithProvider(search, query); } else { window.location.href = search.defaultProvider + query; } @@ -75,32 +98,11 @@ const SearchBar = ({ search }: ISearchBarProps) => { e.preventDefault(); }; - const handleQueryWithProvider = (query: string) => { - let queryArray: Array = query.split(" "); - let prefix: string = queryArray[0]; - - queryArray.shift(); - - let searchQuery: string = queryArray.join(" "); - - let providerFound: boolean = false; - if (search.providers) { - search.providers.forEach((provider: ISearchProviderProps) => { - if (provider.prefix === prefix) { - providerFound = true; - window.location.href = provider.url + searchQuery; - } - }); - } - - if (!providerFound) - window.location.href = search.defaultProvider + query; - }; - return ( handleSearchQuery(e)}> ) => setInput(e.target.value) @@ -108,12 +110,17 @@ const SearchBar = ({ search }: ISearchBarProps) => { > setInput("")} hidden={buttonsHidden} > Clear - diff --git a/src/components/settings.tsx b/src/components/settings.tsx index 3c4d84b..3cba6bc 100644 --- a/src/components/settings.tsx +++ b/src/components/settings.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import { useState } from "react"; import styled from "styled-components"; import Select, { ValueType } from "react-select"; @@ -9,36 +9,37 @@ import { Button, SubHeadline } from "./elements"; import Modal from "./modal"; -const FormContainer = styled.div` +export const FormContainer = styled.div` display: grid; grid-template-columns: auto auto auto; `; -const Table = styled.table` +export const Table = styled.table` font-weight: 400; background: none; width: 100%; color: ${selectedTheme.mainColor}; `; -const TableRow = styled.tr` +export const TableRow = styled.tr` border-bottom: 1px solid ${selectedTheme.mainColor}; `; -const TableCell = styled.td` +export const TableCell = styled.td` + background: none; padding-top: 0.5rem; `; -const HeadCell = styled.th` +export const HeadCell = styled.th` font-weight: 700; text-align: left; `; -const Section = styled.div` +export const Section = styled.div` padding: 1rem 0; `; -const SectionHeadline = styled(SubHeadline)` +export const SectionHeadline = styled(SubHeadline)` width: 100%; border-bottom: 1px solid ${selectedTheme.accentColor}; margin-bottom: 0.5rem; @@ -54,7 +55,7 @@ const Code = styled.p` color: ${selectedTheme.accentColor}; `; -const SelectorStyle: any = { +export const SelectorStyle: any = { container: (base: any): any => ({ ...base, margin: "0 2px", @@ -72,15 +73,15 @@ const SelectorStyle: any = { boxShadow: "none", "&:hover": { border: "1px solid", - borderColor: selectedTheme.mainColor + borderColor: selectedTheme.mainColor, }, }), dropdownIndicator: (base: any): any => ({ ...base, color: selectedTheme.mainColor, "&:hover": { - color: selectedTheme.mainColor - } + color: selectedTheme.mainColor, + }, }), indicatorSeparator: () => ({ display: "none", @@ -91,7 +92,7 @@ const SelectorStyle: any = { border: "1px solid " + selectedTheme.mainColor, borderRadius: 0, boxShadow: "none", - margin: "4px 0" + margin: "4px 0", }), option: (base: any): any => ({ ...base, @@ -125,7 +126,7 @@ interface ISettingsProps { const Settings = ({ themes, search }: ISettingsProps) => { const [newTheme, setNewTheme] = useState(); - if (themes && search) { + if (themes || search) { return ( {themes && ( @@ -133,6 +134,7 @@ const Settings = ({ themes, search }: ISettingsProps) => { Theme: