feat: new popup page with shadcn ui

This commit is contained in:
DIYgod 2023-12-12 02:30:44 +08:00
parent 6a73d1e6eb
commit ee232e8826
No known key found for this signature in database
15 changed files with 1087 additions and 123 deletions

View File

@ -21,7 +21,7 @@
"message": "Copy"
},
"currentPageRSS": {
"message": "RSS on the current site"
"message": "RSS on the current page"
},
"currentPageRSSHub": {
"message": "RSSHub for the current page"

16
components.json Normal file
View File

@ -0,0 +1,16 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/utils/style.css",
"baseColor": "orange",
"cssVariables": true
},
"aliases": {
"components": "~/utils/components",
"utils": "~/utils/utils"
}
}

View File

@ -13,13 +13,19 @@
"@iconify-json/mingcute": "^1.1.13",
"@plasmohq/messaging": "^0.6.1",
"@plasmohq/storage": "^1.9.0",
"@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"lodash": "^4.17.21",
"lucide-react": "^0.294.0",
"plasmo": "0.84.0",
"psl": "^1.9.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"route-recognizer": "^0.3.4",
"rss-parser": "^3.13.0"
"rss-parser": "^3.13.0",
"tailwind-merge": "^2.1.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "4.1.1",
@ -31,6 +37,7 @@
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"prettier": "3.0.3",
"shadcn-ui": "^0.4.1",
"tailwindcss": "^3.3.6",
"typescript": "5.2.2"
},

File diff suppressed because it is too large Load Diff

View File

@ -52,6 +52,10 @@ export const getRSS = async (tabId, url) => {
}
}
export const getCachedRSS = (tabId) => {
return savedRSS[tabId]
}
export const setRSS = (tabId, data: {
pageRSS: RSSData[],
pageRSSHub: RSSData[],
@ -84,6 +88,7 @@ chrome.tabs.onActivated.addListener((tab) => {
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.url && tab.active) {
delete savedRSS[tabId];
getRSS(tabId, changeInfo.url);
}
})

View File

@ -0,0 +1,15 @@
import type { PlasmoMessaging } from "@plasmohq/messaging"
import { getCachedRSS } from "~/background"
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
console.log("popup ready", req)
chrome.tabs.query({
active: true,
lastFocusedWindow: true
}, ([tab]) => {
res.send(getCachedRSS(tab.id))
});
}
export default handler

View File

@ -1,34 +0,0 @@
import { useState } from "react"
import "./style.css"
function IndexPopup() {
const [data, setData] = useState("")
return (
<div className="min-w-[350px] p-5">
<a className="absolute right-4" href="/options.html" target="_blank">
<i className="icon-[mingcute--settings-3-fill] w-5 h-5 text-slate-600 hover:text-black transition-colors"></i>
</a>
<div className="space-y-4">
<h2 className="text-base font-bold">( ´)(._.`) <span>{chrome.i18n.getMessage("RSSNotFound")}</span></h2>
<p dangerouslySetInnerHTML={{
__html: chrome.i18n.getMessage("joinRSSHub")
}}></p>
</div>
<div className="space-y-4 hidden">
<h2 className="text-base font-bold">{chrome.i18n.getMessage("currentPageRSS")}</h2>
<ul></ul>
</div>
<div className="space-y-4 hidden">
<h2 className="text-base font-bold">{chrome.i18n.getMessage("currentPageRSSHub")}</h2>
<ul></ul>
</div>
<div className="space-y-4 hidden">
<h2 className="text-base font-bold">{chrome.i18n.getMessage("currentSiteRSSHub")}</h2>
<ul></ul>
</div>
</div>
)
}
export default IndexPopup

139
src/popup/RSSItem.tsx Normal file
View File

@ -0,0 +1,139 @@
import { useState } from "react"
import { defaultConfig, getConfig } from "~/utils/config"
import type { RSSData } from "~/utils/types"
import { Button } from "~/utils/components/Button"
function RSSItem({ item }: { item: RSSData }) {
const [config, setConfig] = useState(defaultConfig)
getConfig().then(setConfig)
return (
<li className="flex mb-4 items-center space-x-2">
<img className="w-8 h-8" src={item.image || "./rsshub.png"} />
<a href={item.url} className="w-48 cursor-pointer flex flex-col justify-between text-black">
<span className="text-[13px] truncate">{item.title}</span>
<span className="text-xs truncate text-zinc-400">
{item.url.replace("https://", "").replace("http://", "")}
</span>
</a>
{item.isDocs ? (
<a href={item.url} className="rss-action">
{chrome.i18n.getMessage("documentation")}
</a>
) : (
<Button data-clipboard-text={item.url}>
{chrome.i18n.getMessage("copy")}
</Button>
)}
{config.submitto.checkchan && config.submitto.checkchanBase ? (
<a
href={`${config.submitto.checkchanBase.replace(/\/$/, '')}/index.html#/check/add?title=${encodeURI(item.title)}&url=${encodeURI(item.url)}&type=rss&icon=${encodeURI(
item.image
)}`}
className="rss-action rss-submitto-checkchan">
{chrome.i18n.getMessage("subscribe to")} CheckChan
</a>
) : (
""
)}
{config.submitto.ttrss && config.submitto.ttrssDomain ? (
<a
href={`{config.submitto.ttrssDomain.replace(/\/$/, '')}/public.php?op=bookmarklets--subscribe&feed_url=${encodeURI(item.url)}`}
className="rss-action rss-submitto-ttrss">
{chrome.i18n.getMessage("subscribe to")} TTRSS
</a>
) : (
""
)}
{config.submitto.miniflux && config.submitto.minifluxDomain ? (
<a
href={`${config.submitto.minifluxDomain.replace(/\/$/, '')}/bookmarklet?uri=${encodeURI(item.url)}`}
className="rss-action rss-submitto-miniflux">
{chrome.i18n.getMessage("subscribe to")} Miniflux
</a>
) : (
""
)}
{config.submitto.freshrss && config.submitto.freshrssDomain ? (
<a
href={`${config.submitto.freshrssDomain.replace(/\/$/, '')}/i/?c=feed&a=add&url_rss=${encodeURI(item.url)}`}
className="rss-action rss-submitto-freshrss">
{chrome.i18n.getMessage("subscribe to")} FreshRSS
</a>
) : (
""
)}
{config.submitto.nextcloudnews && config.submitto.nextcloudnewsDomain ? (
<a
href={`${config.submitto.nextcloudnewsDomain.replace(/\/$/, '')}/?subscribe_to=${encodeURI(item.url)}`}
className="rss-action rss-submitto-nextcloudnews">
{chrome.i18n.getMessage("subscribe to")} Nextcloud News
</a>
) : (
""
)}
{config.submitto.feedly
? <a href={`https://feedly.com/i/subscription/feed/${encodeURI(
item.url
)}`} className="rss-action rss-submitto-feedly">{chrome.i18n.getMessage(
"subscribe to"
)} Feedly</a>
: ""}
{config.submitto.inoreader && config.submitto.inoreaderDomain ? (
<a
href={`${config.submitto.inoreaderDomain.replace(/\/$/, '')}/?add_feed=${encodeURI(item.url)}`}
className="rss-action rss-submitto-inoreader">
{chrome.i18n.getMessage("subscribe to")} Inoreader
</a>
) : (
""
)}
{config.submitto.feedbin && config.submitto.feedbinDomain ? (
<a
href={`${config.submitto.feedbinDomain.replace(/\/$/, '')}/?subscribe=${encodeURI(item.url)}`}
className="rss-action rss-submitto-feedbin">
{chrome.i18n.getMessage("subscribe to")} Feedbin
</a>
) : (
""
)}
{config.submitto.theoldreader ? (
<a
href={`https://theoldreader.com/feeds/subscribe?url=${encodeURI(item.url)}`}
className="rss-action rss-submitto-theoldreader">
{chrome.i18n.getMessage("subscribe to")} The Old Reader
</a>
) : (
""
)}
{config.submitto.feedspub ? (
<a
href={`https://feeds.pub/feed/${encodeURIComponent(item.url)}`}
className="rss-action rss-submitto-feedspub">
{chrome.i18n.getMessage("subscribe to")} Feeds.Pub
</a>
) : (
""
)}
{config.submitto.bazqux ? (
<a
href={`https://bazqux.com/add?url=${encodeURIComponent(item.url)}`}
className="rss-action rss-submitto-bazqux">
{chrome.i18n.getMessage("subscribe to")} BazQux Reader
</a>
) : (
""
)}
{config.submitto.local ? (
<a href={`feed://${item.url}`} className="rss-action rss-submitto-local">
{chrome.i18n.getMessage("subscribe to local reader")}
</a>
) : (
""
)}
</li>
)
}
export default RSSItem

26
src/popup/RSSList.tsx Normal file
View File

@ -0,0 +1,26 @@
import RSSItem from './RSSItem'
import type { RSSData } from '~/utils/types'
function RSSList({
title,
list,
}: {
title: string
list: RSSData[]
}) {
if (list.length === 0) {
return null
}
return (
<div className="space-y-4">
<h2 className="text-base font-bold">{chrome.i18n.getMessage(title)}</h2>
<ul>
{list.map((item) => (
<RSSItem key={item.url} item={item} />
))}
</ul>
</div>
)
}
export default RSSList

44
src/popup/index.tsx Normal file
View File

@ -0,0 +1,44 @@
import { useState } from "react"
import "~/utils/style.css"
import { sendToBackground } from "@plasmohq/messaging"
import RSSList from "./RSSList"
function IndexPopup() {
const [data, setData] = useState({
pageRSS: [],
pageRSSHub: [],
websiteRSSHub: [],
})
sendToBackground({
name: "popupReady",
}).then((res) => {
console.log(res)
setData(Object.assign({
pageRSS: [],
pageRSSHub: [],
websiteRSSHub: [],
}, res))
})
console.log("data", data)
return (
<div className="min-w-[350px] p-5">
<a className="absolute right-4" href="/options.html" target="_blank">
<i className="icon-[mingcute--settings-3-fill] w-5 h-5 text-slate-600 hover:text-black transition-colors"></i>
</a>
{!data.pageRSS && !data.pageRSSHub && !data.websiteRSSHub && (
<div className="space-y-4">
<h2 className="text-base font-bold">( ´)(._.`) <span>{chrome.i18n.getMessage("RSSNotFound")}</span></h2>
<p dangerouslySetInnerHTML={{
__html: chrome.i18n.getMessage("joinRSSHub")
}}></p>
</div>
)}
<RSSList title="currentPageRSS" list={data.pageRSS} />
<RSSList title="currentPageRSSHub" list={data.pageRSSHub} />
<RSSList title="currentSiteRSSHub" list={data.websiteRSSHub} />
</div>
)
}
export default IndexPopup

View File

@ -1,11 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
font-size: 14px;
}
a {
color: #f5712c;
}

View File

@ -0,0 +1,56 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "~/utils/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

76
src/utils/style.css Normal file
View File

@ -0,0 +1,76 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

6
src/utils/utils.ts Normal file
View File

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@ -2,10 +2,75 @@ import { addDynamicIconSelectors } from "@iconify/tailwind"
/** @type {import('tailwindcss').Config} */
module.exports = {
mode: "jit",
darkMode: "class",
darkMode: ["class"],
content: ["./**/*.tsx"],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [
addDynamicIconSelectors(),
]
require("tailwindcss-animate")
],
}