feat: new popup page with shadcn ui
This commit is contained in:
parent
6a73d1e6eb
commit
ee232e8826
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
700
pnpm-lock.yaml
700
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -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);
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #f5712c;
|
||||
}
|
||||
|
|
@ -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 }
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
|
@ -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")
|
||||
],
|
||||
}
|
||||
Loading…
Reference in New Issue