diff --git a/ui/package.json b/ui/package.json index 25928818..a91756e9 100644 --- a/ui/package.json +++ b/ui/package.json @@ -19,6 +19,7 @@ "@uiw/react-md-editor": "^3.23.5", "antd": "^5.4.7", "buffer": "^6.0.3", + "diff": "^5.1.0", "easymde": "^2.18.0", "file-saver": "^2.0.5", "fs-extra": "^11.1.1", @@ -74,6 +75,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", + "@types/diff": "^5.0.4", "@types/markdown-it": "^13.0.0", "@types/marked": "^5.0.1", "@types/node": "^20.4.4", diff --git a/ui/src/api/API.ts b/ui/src/api/API.ts index 3b951e9e..80c82e1e 100644 --- a/ui/src/api/API.ts +++ b/ui/src/api/API.ts @@ -18,6 +18,8 @@ async function makeRequest(url: string, method = "GET", data = null) { } if (contentType && contentType.includes("application/json")) { return await response.json(); + } else if (contentType && contentType.includes("application/xml")) { + return await response.text(); } else { return await response.text(); } diff --git a/ui/src/components/General/ListItems/News/News.tsx b/ui/src/components/General/ListItems/News/News.tsx index 1e8b3dda..2ce4677a 100644 --- a/ui/src/components/General/ListItems/News/News.tsx +++ b/ui/src/components/General/ListItems/News/News.tsx @@ -1,5 +1,5 @@ import { NewsType } from "./utils/types"; -import { Card, Skeleton, Typography } from "antd"; +import { Card, Image, Skeleton, Space, Typography } from "antd"; import { ListItemProps } from "components/RenderProps/List/utils/types"; const { Title } = Typography; @@ -8,13 +8,20 @@ const News: React.FC> = ({ handleOnClick, isLoading, }) => { - const { title, content, url } = resource || {}; + const { title, content, url, image } = resource || {}; return ( handleOnClick(url)} hoverable> - {title} - {content} + + {image && ( + + )} +
+ {title} + {content} +
+
); diff --git a/ui/src/components/General/ListItems/News/utils/types.ts b/ui/src/components/General/ListItems/News/utils/types.ts index cfaec190..82dd494e 100644 --- a/ui/src/components/General/ListItems/News/utils/types.ts +++ b/ui/src/components/General/ListItems/News/utils/types.ts @@ -2,6 +2,7 @@ interface NewsType { title: string; content: string; url: string; + image?: string; } export type { NewsType }; diff --git a/ui/src/components/General/Search/CategoryTags/utils/helper.ts b/ui/src/components/General/Search/CategoryTags/utils/helper.ts index bcb3bc79..4cbd1372 100644 --- a/ui/src/components/General/Search/CategoryTags/utils/helper.ts +++ b/ui/src/components/General/Search/CategoryTags/utils/helper.ts @@ -1,5 +1,5 @@ -import { QUERY_KEY_NEWS } from "pages/Newsfeed"; import { ResourceType } from "components/General/ListItems/Resource/utils/types"; +import { QUERY_KEY_NEWS } from "pages/Newsfeed/utils/constants"; const getCategories = ( items: T[], diff --git a/ui/src/components/Layouts/Menu/utils/constants.ts b/ui/src/components/Layouts/Menu/utils/constants.ts index f743422e..b1d230c8 100644 --- a/ui/src/components/Layouts/Menu/utils/constants.ts +++ b/ui/src/components/Layouts/Menu/utils/constants.ts @@ -61,12 +61,6 @@ export const MENU_ITEMS = [ icon: "Box", show: IN_DEVELOPMENT, }, - { - name: routesById.svgformatter.title, - url: routesById.svgformatter.path, - icon: "Command", - show: true, - }, ], }, { @@ -123,12 +117,6 @@ export const MENU_ITEMS = [ icon: "QrCode", show: true, }, - { - name: routesById.sorting.title, - url: routesById.sorting.path, - icon: "ArrowUpNarrowWide", - show: true, - }, ], }, { @@ -169,6 +157,31 @@ export const MENU_ITEMS = [ }, ], }, + { + name: "Tools", + icon: "Wrench", + show: true, + children: [ + { + name: routesById.svgformatter.title, + url: routesById.svgformatter.path, + icon: "Command", + show: true, + }, + { + name: routesById.sorting.title, + url: routesById.sorting.path, + icon: "ArrowUpNarrowWide", + show: true, + }, + { + name: routesById.diffchecker.title, + url: routesById.diffchecker.path, + icon: "Diff", + show: true, + }, + ], + }, { name: "Information", icon: "BadgeInfo", diff --git a/ui/src/components/RenderProps/ListSearchResults/index.tsx b/ui/src/components/RenderProps/ListSearchResults/index.tsx index b420ede0..10f9bb97 100644 --- a/ui/src/components/RenderProps/ListSearchResults/index.tsx +++ b/ui/src/components/RenderProps/ListSearchResults/index.tsx @@ -1,7 +1,6 @@ import { useSearchParams } from "react-router-dom"; import style from "./ListSearchResults.module.scss"; import { ResourceType } from "components/General/ListItems/Resource/utils/types"; -import { QUERY_KEY_NEWS } from "pages/Newsfeed"; import Search from "components/General/Search"; import { getCategories } from "components/General/Search/CategoryTags/utils/helper"; import { ListSearchResultsProps } from "./utils/types"; @@ -11,6 +10,7 @@ import Text from "components/General/Text/Text"; import { Typography } from "antd"; import { filteredNews, filteredResource } from "./utils/helper"; import { ReactElement } from "react"; +import { QUERY_KEY_NEWS } from "pages/Newsfeed/utils/constants"; const { Title } = Typography; diff --git a/ui/src/pages/Colors/ColorPicker/ColorPicker.module.scss b/ui/src/pages/Colors/ColorPicker/ColorPicker.module.scss index e580236f..010b9c23 100644 --- a/ui/src/pages/Colors/ColorPicker/ColorPicker.module.scss +++ b/ui/src/pages/Colors/ColorPicker/ColorPicker.module.scss @@ -1,6 +1,6 @@ .cp { display: grid; - grid-template-columns: 380px 360px 400px 380px; + grid-template-columns: 380px 360px 425px 425px; gap: var(--bt-size-16); justify-content: center; diff --git a/ui/src/pages/Colors/ColorPicker/components/DisplayColor/index.tsx b/ui/src/pages/Colors/ColorPicker/components/DisplayColor/index.tsx index ac0a0ae5..2cdffc35 100644 --- a/ui/src/pages/Colors/ColorPicker/components/DisplayColor/index.tsx +++ b/ui/src/pages/Colors/ColorPicker/components/DisplayColor/index.tsx @@ -5,6 +5,7 @@ import ClipboardButton from "components/General/ClipboardButton"; import { classNames, getTextColor, isTransparent } from "lib/utils/helper"; import { DisplayColorProps } from "pages/Colors/ColorPicker/utils/types"; import { useSearchParams } from "react-router-dom"; +import CodeHighlightWithCopy from "components/General/CodeHighlightWithCopy"; const { Title } = Typography; @@ -14,6 +15,7 @@ const DisplayColor: React.FC = ({ customValue, value, format, + title, }) => { const { token: { colorBgContainer, colorText }, @@ -35,6 +37,7 @@ const DisplayColor: React.FC = ({ const containerStyle = { backgroundColor: color ? backgroundColor : colorBgContainer, border: color ? border : "none", + padding: "5px", }; const titleStyle = { @@ -42,11 +45,23 @@ const DisplayColor: React.FC = ({ }; return ( -
- - {customLabel}: {color ? customValue : ""} - - +
+ {title === "Colors" ? ( +
+ + {customLabel}: {color ? customValue : ""} + + +
+ ) : ( + + )}
); }; diff --git a/ui/src/pages/Colors/ColorPicker/components/DisplayColors/index.tsx b/ui/src/pages/Colors/ColorPicker/components/DisplayColors/index.tsx index 246f5b7d..bd421718 100644 --- a/ui/src/pages/Colors/ColorPicker/components/DisplayColors/index.tsx +++ b/ui/src/pages/Colors/ColorPicker/components/DisplayColors/index.tsx @@ -38,6 +38,7 @@ const DisplayColors: React.FC = ({ customValue={determineValue(value, displayType, colors)} value={colors[value]} format={format} + title={title} /> ))} diff --git a/ui/src/pages/Colors/ColorPicker/utils/types.ts b/ui/src/pages/Colors/ColorPicker/utils/types.ts index d7b00c31..01ddb970 100644 --- a/ui/src/pages/Colors/ColorPicker/utils/types.ts +++ b/ui/src/pages/Colors/ColorPicker/utils/types.ts @@ -19,6 +19,7 @@ interface DisplayColorProps { customValue: string; value: string; format: string; + title: string; } export type { FormatType, DisplayColorsProps, DisplayColorProps, colors }; diff --git a/ui/src/pages/Newsfeed/index.tsx b/ui/src/pages/Newsfeed/index.tsx index ac3c47bb..330a66c4 100644 --- a/ui/src/pages/Newsfeed/index.tsx +++ b/ui/src/pages/Newsfeed/index.tsx @@ -1,21 +1,30 @@ -import News from "components/General/ListItems/News/News"; -import useFetchList from "lib/utils/hooks/useFetchList"; +import React from "react"; +import { QUERY_KEY_NEWS, TAB_ITEMS } from "./utils/constants"; +import { Tabs } from "antd"; import ListSearchResults from "components/RenderProps/ListSearchResults"; +import News from "components/General/ListItems/News/News"; +import useNewsFeed from "./utils/hooks"; -const URL = `https://raw.githubusercontent.com/lifeparticle/binarytree/main/api/news.json`; -export const QUERY_KEY_NEWS = "news"; - -const Newsfeed = () => { - const { data, isLoading, isError } = useFetchList(QUERY_KEY_NEWS, URL); +const Newsfeed: React.FC = () => { + const { data, isLoading, isError, setUrl } = useNewsFeed(); return ( - + <> + { + setUrl(value); + }} + /> + + + ); }; diff --git a/ui/src/pages/Newsfeed/utils/constants.ts b/ui/src/pages/Newsfeed/utils/constants.ts new file mode 100644 index 00000000..1538db51 --- /dev/null +++ b/ui/src/pages/Newsfeed/utils/constants.ts @@ -0,0 +1,39 @@ +import { TabsProps } from "antd"; + +const SITE_OPTIONS = { + "frontend-focus": { + label: "Frontend Focus", + value: "https://cprss.s3.amazonaws.com/frontendfoc.us.xml", + }, + "status-code": { + label: "Status Code", + value: "https://cprss.s3.amazonaws.com/react.statuscode.com.xml", + }, + news: { + label: "News", + value: "https://raw.githubusercontent.com/lifeparticle/binarytree/main/api/news.json", + }, +}; + +const TAB_ITEMS: TabsProps["items"] = [ + { + key: SITE_OPTIONS["frontend-focus"].value, + label: SITE_OPTIONS["frontend-focus"].label, + show: true, + }, + { + key: SITE_OPTIONS["status-code"].value, + label: SITE_OPTIONS["status-code"].label, + show: true, + }, + { + key: SITE_OPTIONS["news"].value, + label: SITE_OPTIONS["news"].label, + show: true, + }, +].filter((item) => item.show); +const CORS_PROXY_URL = "https://cors-anywhere.herokuapp.com/"; + +const QUERY_KEY_NEWS = "news"; + +export { SITE_OPTIONS, TAB_ITEMS, CORS_PROXY_URL, QUERY_KEY_NEWS }; diff --git a/ui/src/pages/Newsfeed/utils/helper.ts b/ui/src/pages/Newsfeed/utils/helper.ts new file mode 100644 index 00000000..8fe3b6b4 --- /dev/null +++ b/ui/src/pages/Newsfeed/utils/helper.ts @@ -0,0 +1,27 @@ +function parseXML(value: string) { + const parser = new DOMParser(); + const xmldoc = parser.parseFromString(value, "text/xml"); + + const items = xmldoc.getElementsByTagName("item"); + const list = []; + + for (let i = 0; i < items.length; i++) { + const item = items[i]; + + // Extract data from the 'item' element + const title = item.getElementsByTagName("title")[0].textContent; + const description = parser.parseFromString( + item.getElementsByTagName("description")[0].textContent!, + "text/html" + ); + const image = description?.getElementsByTagName("img")?.[0]?.src; + const pubDate = item.getElementsByTagName("pubDate")[0].textContent; + const url = item.getElementsByTagName("link")[0].textContent; + + list.push({ title, pubDate, url, image }); + } + + return list; +} + +export { parseXML }; diff --git a/ui/src/pages/Newsfeed/utils/hooks.ts b/ui/src/pages/Newsfeed/utils/hooks.ts new file mode 100644 index 00000000..360d09bd --- /dev/null +++ b/ui/src/pages/Newsfeed/utils/hooks.ts @@ -0,0 +1,21 @@ +import { useState } from "react"; +import { parseXML } from "./helper"; +import { CORS_PROXY_URL, SITE_OPTIONS } from "./constants"; +import useFetchList from "lib/utils/hooks/useFetchList"; + +const useNewsFeed = () => { + const [url, setUrl] = useState(SITE_OPTIONS["frontend-focus"].value); + const isFeedUrl = url.includes(".xml"); + + const { data, isLoading, isError } = useFetchList( + url, + isFeedUrl && import.meta.env.DEV ? CORS_PROXY_URL + url : url + ); + let items = undefined; + if (data) { + items = isFeedUrl ? parseXML(data) : data.articles; + } + return { data: items, isLoading, isError, setUrl }; +}; + +export default useNewsFeed; diff --git a/ui/src/pages/Routes/utils/constant.tsx b/ui/src/pages/Routes/utils/constant.tsx index c8432dac..a7131559 100644 --- a/ui/src/pages/Routes/utils/constant.tsx +++ b/ui/src/pages/Routes/utils/constant.tsx @@ -32,6 +32,7 @@ import { TableGenerator, TableOfContent, TextEditor, + Diffchecker, Tool, TvSeries, UiUx, @@ -76,7 +77,7 @@ export const routes: Route[] = [ }, { id: "svgformatter", - path: "/css/svg-formatter", + path: "/tools/svg-formatter", title: "SVG Formatter", description: "Get your SVGs in shipshape.", component: SvgFormatter, @@ -132,7 +133,7 @@ export const routes: Route[] = [ }, { id: "sorting", - path: "/generator/sorting", + path: "/tools/sorting", title: "Sorting", description: "Sort arrays like a boss.", component: Sorting, @@ -264,6 +265,13 @@ export const routes: Route[] = [ description: "Find out what type your file fancies itself as.", component: Mimetype, }, + { + id: "diffchecker", + path: "/tools/textdiff", + title: "Diffchecker", + description: "Find out the difference between text", + component: Diffchecker, + }, { id: "/", path: "/", diff --git a/ui/src/pages/Routes/utils/types.ts b/ui/src/pages/Routes/utils/types.ts index 1624883f..13598c2f 100644 --- a/ui/src/pages/Routes/utils/types.ts +++ b/ui/src/pages/Routes/utils/types.ts @@ -31,6 +31,7 @@ type RouteId = | "tableofcontent" | "texteditor" | "mimetype" + | "diffchecker" | "/" | "about" | "feedback" diff --git a/ui/src/pages/Tools/Diffchecker/Diffchecker.module.scss b/ui/src/pages/Tools/Diffchecker/Diffchecker.module.scss new file mode 100644 index 00000000..35ed64fd --- /dev/null +++ b/ui/src/pages/Tools/Diffchecker/Diffchecker.module.scss @@ -0,0 +1,7 @@ +.diffchecker { + &__button { + display: flex; + justify-content: center; + padding: var(--bt-size-20) 0; + } +} diff --git a/ui/src/pages/Tools/Diffchecker/index.tsx b/ui/src/pages/Tools/Diffchecker/index.tsx new file mode 100644 index 00000000..4983287e --- /dev/null +++ b/ui/src/pages/Tools/Diffchecker/index.tsx @@ -0,0 +1,82 @@ +import { Card, Form, Input } from "antd"; +import PageGrid from "components/Layouts/PageGrid"; +import React, { useState } from "react"; + +import { diffChars, Change } from "diff"; +import { ResponsiveButton } from "components/General/FormComponents"; +import style from "./Diffchecker.module.scss"; + +const { TextArea } = Input; + +const Diffchecker: React.FC = () => { + const [text1, setText1] = useState(""); + const [text2, setText2] = useState(""); + const [differences, setDifferences] = useState([]); + + const calculateDiff = () => { + const diff = diffChars(text1, text2); + setDifferences(diff); + }; + + return ( + <> + + +
+ +