feat: rules page

This commit is contained in:
DIYgod
2023-12-14 03:00:06 +08:00
parent 0f34c056b6
commit d60e40ef8d
15 changed files with 345 additions and 46 deletions
+5 -2
View File
@@ -54,7 +54,7 @@
"message": "Full remote updates are disabled due to browser limitations"
},
"forMoreRulesJoinUs": {
"message": "For more rules support, <a target=\"_blank\" href=\"https://docs.rsshub.app/en/joinus/quick-start.html\">join us</a>"
"message": "For more rules support, <a target=\"_blank\" href=\"https://docs.rsshub.app/joinus/quick-start.html\">join us</a>"
},
"general": {
"message": "General"
@@ -78,7 +78,7 @@
"message": "No RSS found"
},
"rules": {
"message": "Rules"
"message": "Radar Rules"
},
"rulesUpdate": {
"message": "Rules update"
@@ -118,5 +118,8 @@
},
"fillInstanceDomain": {
"message": "Please fill in your instance domain"
},
"totalNumberOfRules": {
"message": "Total number of rules"
}
}
+5 -2
View File
@@ -54,7 +54,7 @@
"message": "由于浏览器限制,完整的远程更新被禁用"
},
"forMoreRulesJoinUs": {
"message": "更多规则支持中,快来<a target=\"_blank\" href=\"https://docs.rsshub.app/joinus/quick-start.html\">参与我们</a>吧!"
"message": "更多规则支持中,快来<a target=\"_blank\" href=\"https://docs.rsshub.app/zh/joinus/quick-start.html\">参与我们</a>吧!"
},
"general": {
"message": "常规"
@@ -78,7 +78,7 @@
"message": "没有检测到 RSS"
},
"rules": {
"message": "规则列表"
"message": "Radar 规则"
},
"rulesUpdate": {
"message": "规则更新"
@@ -118,5 +118,8 @@
},
"fillInstanceDomain": {
"message": "请填写你的实例域名"
},
"totalNumberOfRules": {
"message": "规则总数"
}
}
+1
View File
@@ -13,6 +13,7 @@
"@iconify-json/mingcute": "^1.1.13",
"@plasmohq/messaging": "^0.6.1",
"@plasmohq/storage": "^1.9.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
+135
View File
@@ -14,6 +14,9 @@ dependencies:
'@plasmohq/storage':
specifier: ^1.9.0
version: 1.9.0(react@18.2.0)
'@radix-ui/react-accordion':
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-label':
specifier: ^2.0.2
version: 2.0.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
@@ -2411,6 +2414,87 @@ packages:
'@babel/runtime': 7.23.5
dev: false
/@radix-ui/react-accordion@1.1.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.23.5
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collapsible': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@types/react': 18.2.37
'@types/react-dom': 18.2.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.23.5
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@types/react': 18.2.37
'@types/react-dom': 18.2.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.23.5
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.37)(react@18.2.0)
'@types/react': 18.2.37
'@types/react-dom': 18.2.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.37)(react@18.2.0):
resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
peerDependencies:
@@ -2439,6 +2523,35 @@ packages:
react: 18.2.0
dev: false
/@radix-ui/react-direction@1.0.1(@types/react@18.2.37)(react@18.2.0):
resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.23.5
'@types/react': 18.2.37
react: 18.2.0
dev: false
/@radix-ui/react-id@1.0.1(@types/react@18.2.37)(react@18.2.0):
resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.23.5
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@types/react': 18.2.37
react: 18.2.0
dev: false
/@radix-ui/react-label@2.0.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==}
peerDependencies:
@@ -2460,6 +2573,28 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.23.5
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@types/react': 18.2.37
'@types/react-dom': 18.2.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==}
peerDependencies:
+3 -1
View File
@@ -26,6 +26,8 @@ const storage = new Storage({
area: "local"
})
export const getRules = () => storage.get("rules")
export const getRSS = async (tabId, url) => {
console.debug("Get RSS", tabId, url)
@@ -52,7 +54,7 @@ export const getRSS = async (tabId, url) => {
tabId,
html,
url,
rules: await storage.get("rules"),
rules: await getRules(),
}
}
})
+1 -1
View File
@@ -1,7 +1,7 @@
import type { PlasmoMessaging } from "@plasmohq/messaging"
import { getRSS } from "~/background"
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
const handler: PlasmoMessaging.MessageHandler = (req) => {
console.debug("content ready", req)
getRSS(req.sender.tab.id, req.sender.tab.url)
+1 -1
View File
@@ -1,7 +1,7 @@
import type { PlasmoMessaging } from "@plasmohq/messaging"
import { getCachedRSS } from "~/background"
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
const handler: PlasmoMessaging.MessageHandler = (req, res) => {
console.debug("popup ready", req)
chrome.tabs.query({
+10
View File
@@ -0,0 +1,10 @@
import type { PlasmoMessaging } from "@plasmohq/messaging"
import { getRules } from "~/background"
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
console.debug("request rules", req)
res.send(await getRules())
}
export default handler
+58
View File
@@ -0,0 +1,58 @@
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "~/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
+4 -21
View File
@@ -1,12 +1,13 @@
import _ from 'lodash';
import { defaultRules } from './radar-rules';
import { defaultConfig } from './config';
import type { Rules } from './types';
export function parseRules(rules) {
export function parseRules(rules: string, forceJSON?: boolean) {
let incomeRules = rules;
if (incomeRules) {
if (typeof rules === 'string') {
if (defaultConfig.enableFullRemoteRules) {
if (defaultConfig.enableFullRemoteRules && !forceJSON) {
incomeRules = window['lave'.split('').reverse().join('')](rules);
} else {
incomeRules = JSON.parse(rules);
@@ -19,23 +20,5 @@ export function parseRules(rules) {
} else if (_.isFunction(objValue)) {
return objValue;
}
});
}
export function getList(data) {
const rules = parseRules(data.rules);
for (const rule in rules) {
for (const subrule in rules[rule]) {
if (subrule[0] !== '_') {
rules[rule][subrule].forEach((item) => {
delete item.source;
delete item.target;
delete item.script;
delete item.verification;
delete item.test;
});
}
}
}
return rules;
}) as Rules;
}
+4
View File
@@ -73,4 +73,8 @@
body {
@apply bg-background text-foreground;
}
.content a {
@apply underline text-orange-500;
}
}
+1 -18
View File
@@ -1,32 +1,15 @@
import { defaultConfig, setConfig } from "~/lib/config"
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "~/lib/components/Card"
import { Switch } from "~/lib/components/Switch"
import { quickSubscriptions } from "~/lib/quick-subscriptions"
import { useStorage } from "@plasmohq/storage/hook"
import _ from 'lodash';
function About() {
const [config] = useStorage("config", (v) => _.merge({}, defaultConfig, v))
return (
<div>
<h1 className="text-3xl font-medium leading-10 mb-6 text-orange-500 border-b pb-4">{chrome.i18n.getMessage("about")}</h1>
<div className="space-y-4">
<Card>
<CardContent className="space-y-4 content">
<style>{
`
.content a {
text-decoration-line: underline;
color: rgb(249 115 22);
}
`
}</style>
<CardContent className="space-y-4 content text-zinc-700 py-10">
<p dangerouslySetInnerHTML={{
__html: chrome.i18n.getMessage("RSSHubRadarInfo")
}}></p>
+101
View File
@@ -0,0 +1,101 @@
import _ from "lodash"
import { useEffect, useState } from "react"
import { sendToBackground } from "@plasmohq/messaging"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger
} from "~/lib/components/Accordion"
import { Card, CardContent } from "~/lib/components/Card"
import { parseRules } from "~/lib/rules"
import type { Rules as IRules } from "~/lib/types"
function Rules() {
const [rules, setRules] = useState<IRules>({})
useEffect(() => {
sendToBackground({
name: "requestRules"
}).then((res) => setRules(parseRules(res, true)))
}, [])
const [count, setCount] = useState(0)
useEffect(() => {
let index = 0;
Object.keys(rules).map((key) => {
const rule = rules[key]
Object.keys(rule).map((item) => {
if (Array.isArray(rule[item])) {
index += rule[item].length
}
})
})
setCount(index)
}, [rules])
return (
<div>
<h1 className="text-3xl font-medium leading-10 mb-6 text-orange-500 border-b pb-4">
{chrome.i18n.getMessage("rules")}
</h1>
<div className="content mb-6 space-y-2">
<p>{chrome.i18n.getMessage("totalNumberOfRules")}: {count}</p>
<p dangerouslySetInnerHTML={{
__html: chrome.i18n.getMessage("forMoreRulesJoinUs")
}}></p>
</div>
<div className="space-y-4">
<Card>
<CardContent>
{Object.keys(rules).map((key) => {
const rule = rules[key]
return (
<div key={key}>
<Accordion type="single" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger>
<div className="font-medium">
{rule._name} - {key}
</div>
</AccordionTrigger>
<AccordionContent>
<div className="space-y-2">
{Object.keys(rule).map((item) => {
const subdomainRules = rule[item]
if (Array.isArray(subdomainRules)) {
return subdomainRules.map(
(subdomainRule, index) => {
return (
<div key={key + item + index}>
<div className="flex items-center space-x-2">
<div className="flex-1">
<p className="text-sm text-zinc-700">
{subdomainRule.title}
</p>
</div>
</div>
</div>
)
}
)
} else {
return null
}
})}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
)
})}
</CardContent>
</Card>
</div>
</div>
)
}
export { Rules }
+2
View File
@@ -1,11 +1,13 @@
import { Route, Routes } from "react-router-dom"
import { About } from "./About"
import { Rules } from "./Rules"
import { General } from "./General"
export const Routing = () => (
<Routes>
<Route path="/" element={<General />} />
<Route path="/rules" element={<Rules />} />
<Route path="/about" element={<About />} />
</Routes>
)
+14
View File
@@ -24,6 +24,20 @@ module.exports = {
},
},
extend: {
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",
},
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",