mirror of
https://github.com/DIYgod/RSSHub-Radar.git
synced 2026-09-21 05:46:31 +08:00
chore: use prettier
This commit is contained in:
@@ -40,4 +40,4 @@ jobs:
|
||||
with:
|
||||
file: "build/chrome-mv3-prod.zip;build/firefox-mv3-prod.zip;build/safari-mv3-prod.zip"
|
||||
tags: true
|
||||
draft: false
|
||||
draft: false
|
||||
|
||||
@@ -34,4 +34,4 @@ jobs:
|
||||
chrome-file: build/chrome-mv3-prod.zip
|
||||
edge-file: build/chrome-mv3-prod.zip
|
||||
firefox-file: build/firefox-mv3-prod.zip
|
||||
notes: "RSSHub Radar is an open source project, you can find the source code at https://github.com/DIYgod/RSSHub-Radar"
|
||||
notes: "RSSHub Radar is an open source project, you can find the source code at https://github.com/DIYgod/RSSHub-Radar"
|
||||
|
||||
@@ -38,4 +38,4 @@ jobs:
|
||||
path: |
|
||||
build/safari-mv3-prod.zip
|
||||
build/chrome-mv3-prod.zip
|
||||
build/firefox-mv3-prod.zip
|
||||
build/firefox-mv3-prod.zip
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
module.exports = {
|
||||
singleQuote: false,
|
||||
semi: false,
|
||||
trailingComma: "all",
|
||||
endOfLine: "lf",
|
||||
plugins: ["prettier-package-json", "@ianvs/prettier-plugin-sort-imports"],
|
||||
|
||||
importOrderParserPlugins: [
|
||||
"classProperties",
|
||||
"decorators-legacy",
|
||||
"typescript",
|
||||
"jsx",
|
||||
],
|
||||
importOrder: [
|
||||
"<THIRD_PARTY_MODULES>",
|
||||
"",
|
||||
"^@(.*)/(.*)$",
|
||||
"",
|
||||
"^~/(.*)$",
|
||||
"",
|
||||
"^@/(.*)$",
|
||||
"",
|
||||
"^[./]",
|
||||
],
|
||||
}
|
||||
+2
-2
@@ -21,6 +21,6 @@ export default {
|
||||
"",
|
||||
"^~(.*)$",
|
||||
"",
|
||||
"^[./]"
|
||||
]
|
||||
"^[./]",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -149,4 +149,4 @@
|
||||
"current": {
|
||||
"message": "Current"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,4 +149,4 @@
|
||||
"current": {
|
||||
"message": "当前"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,4 +13,4 @@
|
||||
"components": "~/lib/components",
|
||||
"utils": "~/lib/utils"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -13,7 +13,8 @@
|
||||
"safari-convert": "xcrun safari-web-extension-converter build/safari-mv3-prod --project-location build --bundle-identifier app.rsshub.RSSHub-Radar",
|
||||
"safari-zip": "zip -r build/safari-mv3-prod.zip \"build/RSSHub Radar\"",
|
||||
"build:safari:zip": "npm run build:safari && npm run safari-convert && npm run safari-zip",
|
||||
"package": "plasmo package"
|
||||
"package": "plasmo package",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify-json/mingcute": "^1.1.15",
|
||||
@@ -51,12 +52,18 @@
|
||||
"@types/react": "18.2.48",
|
||||
"@types/react-dom": "18.2.18",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"husky": "9.0.9",
|
||||
"lint-staged": "15.2.1",
|
||||
"postcss": "^8.4.33",
|
||||
"prettier": "3.2.4",
|
||||
"prettier-package-json": "2.8.0",
|
||||
"shadcn-ui": "^0.8.0",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*": "prettier --write --ignore-unknown"
|
||||
},
|
||||
"manifest": {
|
||||
"default_locale": "en",
|
||||
"host_permissions": [
|
||||
|
||||
Generated
+5185
-2295
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -4,6 +4,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { getConfig } from "~/lib/config"
|
||||
|
||||
chrome.action.setBadgeBackgroundColor({
|
||||
color: '#F62800',
|
||||
});
|
||||
color: "#F62800",
|
||||
})
|
||||
|
||||
chrome.action.setBadgeTextColor({
|
||||
color: '#fff',
|
||||
});
|
||||
color: "#fff",
|
||||
})
|
||||
|
||||
export const setBadge = async (text: string, tabId: number) => {
|
||||
const config = await getConfig()
|
||||
|
||||
|
||||
if (config.notice.badge) {
|
||||
chrome.action.setBadgeText({
|
||||
text,
|
||||
tabId,
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getRSS, deleteCachedRSS } from "./rss"
|
||||
import { deleteCachedRSS, getRSS } from "./rss"
|
||||
import { initSchedule } from "./rules"
|
||||
import { initUpdateNotifications } from "./update-notifications"
|
||||
|
||||
@@ -7,27 +7,27 @@ export {}
|
||||
chrome.tabs.onActivated.addListener((tab) => {
|
||||
chrome.tabs.get(tab.tabId, (info) => {
|
||||
if (info.url) {
|
||||
getRSS(tab.tabId, info.url);
|
||||
getRSS(tab.tabId, info.url)
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if (tab.active) {
|
||||
if (changeInfo.url) {
|
||||
deleteCachedRSS(tabId)
|
||||
getRSS(tabId, changeInfo.url);
|
||||
getRSS(tabId, changeInfo.url)
|
||||
} else if (changeInfo.status === "loading") {
|
||||
deleteCachedRSS(tabId)
|
||||
} else if (changeInfo.status === "complete") {
|
||||
getRSS(tabId, tab.url);
|
||||
getRSS(tabId, tab.url)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
chrome.tabs.onRemoved.addListener((tabId) => {
|
||||
deleteCachedRSS(tabId)
|
||||
});
|
||||
})
|
||||
|
||||
initSchedule();
|
||||
initUpdateNotifications();
|
||||
initSchedule()
|
||||
initUpdateNotifications()
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { PlasmoMessaging } from "@plasmohq/messaging"
|
||||
|
||||
import { getRSS } from "~/background/rss"
|
||||
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler = (req, res) => {
|
||||
getRSS(req.sender.tab.id, req.sender.tab.url)
|
||||
res.send('')
|
||||
res.send("")
|
||||
}
|
||||
|
||||
export default handler
|
||||
export default handler
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import type { PlasmoMessaging } from "@plasmohq/messaging"
|
||||
|
||||
import { getCachedRSS } from "~/background/rss"
|
||||
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler = (req, res) => {
|
||||
chrome.tabs.query({
|
||||
active: true,
|
||||
lastFocusedWindow: true
|
||||
}, ([tab]) => {
|
||||
res.send(getCachedRSS(tab.id))
|
||||
});
|
||||
chrome.tabs.query(
|
||||
{
|
||||
active: true,
|
||||
lastFocusedWindow: true,
|
||||
},
|
||||
([tab]) => {
|
||||
res.send(getCachedRSS(tab.id))
|
||||
},
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
export default handler
|
||||
export default handler
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { PlasmoMessaging } from "@plasmohq/messaging"
|
||||
|
||||
import { refreshRules } from "~/background/rules"
|
||||
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler = (req, res) => {
|
||||
refreshRules().then(() => {
|
||||
res.send(true)
|
||||
@@ -8,4 +9,4 @@ const handler: PlasmoMessaging.MessageHandler = (req, res) => {
|
||||
return true
|
||||
}
|
||||
|
||||
export default handler
|
||||
export default handler
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { PlasmoMessaging } from "@plasmohq/messaging"
|
||||
|
||||
import { getDisplayedRules } from "~/background/rules"
|
||||
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler = (req, res) => {
|
||||
getDisplayedRules().then((rules) => {
|
||||
res.send(rules)
|
||||
@@ -8,4 +9,4 @@ const handler: PlasmoMessaging.MessageHandler = (req, res) => {
|
||||
return true
|
||||
}
|
||||
|
||||
export default handler
|
||||
export default handler
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { PlasmoMessaging } from "@plasmohq/messaging"
|
||||
|
||||
import { setDisplayedRules } from "~/background/rules"
|
||||
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler = (req, res) => {
|
||||
setDisplayedRules(req.body.displayedRules)
|
||||
res.send('')
|
||||
res.send("")
|
||||
}
|
||||
|
||||
export default handler
|
||||
export default handler
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { PlasmoMessaging } from "@plasmohq/messaging"
|
||||
|
||||
import { setRSS } from "~/background/rss"
|
||||
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
|
||||
setRSS(req.body.tabId, req.body.rss)
|
||||
res.send('')
|
||||
res.send("")
|
||||
}
|
||||
|
||||
export default handler
|
||||
export default handler
|
||||
|
||||
+10
-10
@@ -4,14 +4,14 @@ import { sendToContentScript } from "@plasmohq/messaging"
|
||||
import { Storage } from "@plasmohq/storage"
|
||||
|
||||
import { setupOffscreenDocument } from "~/lib/offscreen"
|
||||
import report from "~/lib/report"
|
||||
import type { RSSData } from "~/lib/types"
|
||||
import { getRSS as sandboxGetRSS } from "~/sandboxes"
|
||||
import report from "~/lib/report"
|
||||
|
||||
import { setBadge } from "./badge"
|
||||
|
||||
const storage = new Storage({
|
||||
area: "local"
|
||||
area: "local",
|
||||
})
|
||||
|
||||
const savedRSS: {
|
||||
@@ -35,7 +35,7 @@ export const getRSS = async (tabId, url) => {
|
||||
})
|
||||
const html = await sendToContentScript({
|
||||
name: "requestHTML",
|
||||
tabId
|
||||
tabId,
|
||||
})
|
||||
|
||||
if (chrome.offscreen) {
|
||||
@@ -48,9 +48,9 @@ export const getRSS = async (tabId, url) => {
|
||||
tabId,
|
||||
html,
|
||||
url,
|
||||
rules: await storage.get("rules")
|
||||
}
|
||||
}
|
||||
rules: await storage.get("rules"),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
@@ -59,7 +59,7 @@ export const getRSS = async (tabId, url) => {
|
||||
html,
|
||||
url,
|
||||
rules: await storage.get("rules"),
|
||||
callback: (rss) => setRSS(tabId, rss)
|
||||
callback: (rss) => setRSS(tabId, rss),
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -75,7 +75,7 @@ function applyRSS(
|
||||
pageRSS: RSSData[]
|
||||
pageRSSHub: RSSData[]
|
||||
websiteRSSHub: RSSData[]
|
||||
}
|
||||
},
|
||||
) {
|
||||
savedRSS[tabId] = data
|
||||
|
||||
@@ -97,14 +97,14 @@ export const setRSS = async (
|
||||
pageRSS: RSSData[]
|
||||
pageRSSHub: RSSData[]
|
||||
websiteRSSHub: RSSData[]
|
||||
}
|
||||
},
|
||||
) => {
|
||||
applyRSS(tabId, data)
|
||||
|
||||
const res = await sendToContentScript({
|
||||
name: "parseRSS",
|
||||
tabId,
|
||||
body: data.pageRSS.filter((rss) => rss.uncertain).map((rss) => rss.url)
|
||||
body: data.pageRSS.filter((rss) => rss.uncertain).map((rss) => rss.url),
|
||||
})
|
||||
data.pageRSS = data.pageRSS.filter((rss) => {
|
||||
if (rss.uncertain) {
|
||||
|
||||
+17
-15
@@ -1,11 +1,12 @@
|
||||
import { Storage } from "@plasmohq/storage"
|
||||
import { getRemoteRules } from "~/lib/rules"
|
||||
|
||||
import { getConfig } from "~/lib/config"
|
||||
import { getDisplayedRules as sandboxGetDisplayedRules } from "~/sandboxes"
|
||||
import { setupOffscreenDocument } from "~/lib/offscreen"
|
||||
import { getRemoteRules } from "~/lib/rules"
|
||||
import { getDisplayedRules as sandboxGetDisplayedRules } from "~/sandboxes"
|
||||
|
||||
const storage = new Storage({
|
||||
area: "local"
|
||||
area: "local",
|
||||
})
|
||||
|
||||
export const refreshRules = async () => {
|
||||
@@ -19,8 +20,8 @@ export const refreshRules = async () => {
|
||||
name: "requestDisplayedRules",
|
||||
body: {
|
||||
rules,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
const displayedRules = sandboxGetDisplayedRules(rules)
|
||||
@@ -31,27 +32,28 @@ export const refreshRules = async () => {
|
||||
|
||||
export const getDisplayedRules = () => storage.get("displayedRules")
|
||||
|
||||
export const setDisplayedRules = (displayedRules) => storage.set("displayedRules", displayedRules)
|
||||
export const setDisplayedRules = (displayedRules) =>
|
||||
storage.set("displayedRules", displayedRules)
|
||||
|
||||
chrome.alarms.onAlarm.addListener((alarm) => {
|
||||
if (alarm.name === 'refreshRulesAlarm') {
|
||||
refreshRules();
|
||||
if (alarm.name === "refreshRulesAlarm") {
|
||||
refreshRules()
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
export async function initSchedule() {
|
||||
const config = await getConfig();
|
||||
const config = await getConfig()
|
||||
const rules = await storage.get("rules")
|
||||
if (!rules) {
|
||||
setTimeout(() => {
|
||||
refreshRules();
|
||||
}, 60 * 1000);
|
||||
refreshRules()
|
||||
}, 60 * 1000)
|
||||
}
|
||||
|
||||
const alarm = await chrome.alarms.get("refreshRulesAlarm");
|
||||
const alarm = await chrome.alarms.get("refreshRulesAlarm")
|
||||
if (!alarm) {
|
||||
chrome.alarms.create('refreshRulesAlarm', {
|
||||
chrome.alarms.create("refreshRulesAlarm", {
|
||||
periodInMinutes: config.refreshTimeout / 60,
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
import { Storage } from "@plasmohq/storage"
|
||||
import info from "../../package.json"
|
||||
import RSSHubIcon from "data-base64:~/assets/icon.png"
|
||||
|
||||
import { Storage } from "@plasmohq/storage"
|
||||
|
||||
import info from "../../package.json"
|
||||
|
||||
const storage = new Storage({
|
||||
area: "local"
|
||||
area: "local",
|
||||
})
|
||||
|
||||
export const initUpdateNotifications = async () => {
|
||||
const version = await storage.get("version");
|
||||
const version = await storage.get("version")
|
||||
if (!version || version !== info.version) {
|
||||
chrome.notifications.create('RSSHubRadarUpdate', {
|
||||
type: 'basic',
|
||||
chrome.notifications.create("RSSHubRadarUpdate", {
|
||||
type: "basic",
|
||||
iconUrl: RSSHubIcon,
|
||||
title: version ? chrome.i18n.getMessage('extensionUpdateTip') : chrome.i18n.getMessage('extensionInstallTip'),
|
||||
message: `v${info.version}, ${chrome.i18n.getMessage('clickToViewChangeLog')}`,
|
||||
});
|
||||
title: version
|
||||
? chrome.i18n.getMessage("extensionUpdateTip")
|
||||
: chrome.i18n.getMessage("extensionInstallTip"),
|
||||
message: `v${info.version}, ${chrome.i18n.getMessage("clickToViewChangeLog")}`,
|
||||
})
|
||||
chrome.notifications.onClicked.addListener((id) => {
|
||||
if (id === 'RSSHubRadarUpdate') {
|
||||
chrome.tabs.create({
|
||||
url: 'https://github.com/DIYgod/RSSHub-Radar/releases',
|
||||
});
|
||||
chrome.notifications.clear('RSSHubRadarUpdate');
|
||||
}
|
||||
});
|
||||
await storage.set("version", info.version);
|
||||
if (id === "RSSHubRadarUpdate") {
|
||||
chrome.tabs.create({
|
||||
url: "https://github.com/DIYgod/RSSHub-Radar/releases",
|
||||
})
|
||||
chrome.notifications.clear("RSSHubRadarUpdate")
|
||||
}
|
||||
})
|
||||
await storage.set("version", info.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { sendToBackground } from "@plasmohq/messaging"
|
||||
|
||||
import { fetchRSSContent, parseRSS } from "~/lib/utils"
|
||||
|
||||
sendToBackground({
|
||||
@@ -32,4 +33,4 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
}
|
||||
})
|
||||
|
||||
export { }
|
||||
export {}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
import * as React from "react"
|
||||
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
@@ -29,7 +30,7 @@ const AccordionTrigger = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-1 items-center justify-between py-4 font-medium transition-all [&[data-state=open]>svg]:rotate-180",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import * as React from "react"
|
||||
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
@@ -18,8 +19,7 @@ const buttonVariants = cva(
|
||||
"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",
|
||||
rss:
|
||||
"border border-orange-500 text-orange-500 bg-background hover:bg-orange-500 hover:text-white",
|
||||
rss: "border border-orange-500 text-orange-500 bg-background hover:bg-orange-500 hover:text-white",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
@@ -32,7 +32,7 @@ const buttonVariants = cva(
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
@@ -51,7 +51,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ const Card = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -37,7 +37,7 @@ const CardTitle = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-xl font-semibold leading-none tracking-tight",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -12,13 +12,13 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import * as React from "react"
|
||||
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
)
|
||||
|
||||
const Label = React.forwardRef<
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
@@ -12,14 +13,14 @@ const Switch = React.forwardRef<
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
|
||||
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
|
||||
+60
-53
@@ -1,65 +1,72 @@
|
||||
import { Storage } from "@plasmohq/storage"
|
||||
import _ from 'lodash';
|
||||
import _ from "lodash"
|
||||
import toast from "react-hot-toast"
|
||||
|
||||
import { Storage } from "@plasmohq/storage"
|
||||
|
||||
const storage = new Storage()
|
||||
|
||||
const enableFullRemoteRules = !(navigator.userAgent.match(/firefox/i) || (navigator.userAgent.match(/safari/i) && !navigator.userAgent.match(/chrome/i)))
|
||||
const remoteRulesUrl = enableFullRemoteRules ? 'https://rsshub.js.org/build/radar-rules.js' : 'https://rsshub.js.org/build/radar-rules.json'
|
||||
const enableFullRemoteRules = !(
|
||||
navigator.userAgent.match(/firefox/i) ||
|
||||
(navigator.userAgent.match(/safari/i) &&
|
||||
!navigator.userAgent.match(/chrome/i))
|
||||
)
|
||||
const remoteRulesUrl = enableFullRemoteRules
|
||||
? "https://rsshub.js.org/build/radar-rules.js"
|
||||
: "https://rsshub.js.org/build/radar-rules.json"
|
||||
|
||||
export const defaultConfig = {
|
||||
rsshubDomain: 'https://rsshub.app',
|
||||
rsshubAccessControl: {
|
||||
accessKey: '',
|
||||
},
|
||||
notice: {
|
||||
badge: true,
|
||||
},
|
||||
submitto: {
|
||||
ttrss: false,
|
||||
ttrssDomain: '',
|
||||
checkchan: false,
|
||||
checkchanBase: '',
|
||||
miniflux: false,
|
||||
minifluxDomain: '',
|
||||
freshrss: false,
|
||||
freshrssDomain: '',
|
||||
nextcloudnews: false,
|
||||
nextcloudnewsDomain: '',
|
||||
feedly: false,
|
||||
inoreader: true,
|
||||
inoreaderDomain: 'https://www.inoreader.com',
|
||||
feedbin: false,
|
||||
feedbinDomain: 'https://feedbin.com',
|
||||
theoldreader: false,
|
||||
qireaderDomain: 'https://www.qireader.com',
|
||||
feedspub: false,
|
||||
bazqux: false,
|
||||
local: true,
|
||||
},
|
||||
refreshTimeout: 2 * 60 * 60,
|
||||
enableFullRemoteRules,
|
||||
remoteRulesUrl,
|
||||
};
|
||||
rsshubDomain: "https://rsshub.app",
|
||||
rsshubAccessControl: {
|
||||
accessKey: "",
|
||||
},
|
||||
notice: {
|
||||
badge: true,
|
||||
},
|
||||
submitto: {
|
||||
ttrss: false,
|
||||
ttrssDomain: "",
|
||||
checkchan: false,
|
||||
checkchanBase: "",
|
||||
miniflux: false,
|
||||
minifluxDomain: "",
|
||||
freshrss: false,
|
||||
freshrssDomain: "",
|
||||
nextcloudnews: false,
|
||||
nextcloudnewsDomain: "",
|
||||
feedly: false,
|
||||
inoreader: true,
|
||||
inoreaderDomain: "https://www.inoreader.com",
|
||||
feedbin: false,
|
||||
feedbinDomain: "https://feedbin.com",
|
||||
theoldreader: false,
|
||||
qireaderDomain: "https://www.qireader.com",
|
||||
feedspub: false,
|
||||
bazqux: false,
|
||||
local: true,
|
||||
},
|
||||
refreshTimeout: 2 * 60 * 60,
|
||||
enableFullRemoteRules,
|
||||
remoteRulesUrl,
|
||||
}
|
||||
|
||||
export async function getConfig() {
|
||||
let storagedConfig = {}
|
||||
try {
|
||||
storagedConfig = await storage.get("config");
|
||||
} catch (error) {}
|
||||
return _.merge({}, defaultConfig, storagedConfig) as typeof defaultConfig;
|
||||
let storagedConfig = {}
|
||||
try {
|
||||
storagedConfig = await storage.get("config")
|
||||
} catch (error) {}
|
||||
return _.merge({}, defaultConfig, storagedConfig) as typeof defaultConfig
|
||||
}
|
||||
|
||||
let toastId: string | undefined;
|
||||
let toastId: string | undefined
|
||||
export async function setConfig(config: Partial<typeof defaultConfig>) {
|
||||
let storagedConfig = {}
|
||||
try {
|
||||
storagedConfig = await storage.get("config");
|
||||
} catch (error) {}
|
||||
config = _.merge({}, storagedConfig, config);
|
||||
await storage.set("config", config)
|
||||
toastId = toast.success("Saved", {
|
||||
id: toastId,
|
||||
})
|
||||
return config;
|
||||
let storagedConfig = {}
|
||||
try {
|
||||
storagedConfig = await storage.get("config")
|
||||
} catch (error) {}
|
||||
config = _.merge({}, storagedConfig, config)
|
||||
await storage.set("config", config)
|
||||
toastId = toast.success("Saved", {
|
||||
id: toastId,
|
||||
})
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ async function setupOffscreenDocument(path) {
|
||||
// @ts-ignore
|
||||
const existingContexts = await chrome.runtime.getContexts({
|
||||
contextTypes: ["OFFSCREEN_DOCUMENT"],
|
||||
documentUrls: [offscreenUrl]
|
||||
documentUrls: [offscreenUrl],
|
||||
})
|
||||
|
||||
if (existingContexts.length > 0) {
|
||||
@@ -18,13 +18,11 @@ async function setupOffscreenDocument(path) {
|
||||
creating = chrome.offscreen.createDocument({
|
||||
url: chrome.runtime.getURL("tabs/offscreen.html"),
|
||||
reasons: [chrome.offscreen.Reason.IFRAME_SCRIPTING],
|
||||
justification: "Get RSS in the sandbox for enhanced security."
|
||||
justification: "Get RSS in the sandbox for enhanced security.",
|
||||
})
|
||||
await creating
|
||||
creating = null
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
setupOffscreenDocument
|
||||
}
|
||||
export { setupOffscreenDocument }
|
||||
|
||||
+114
-95
@@ -9,98 +9,117 @@ export const quickSubscriptions: ({
|
||||
title: string
|
||||
image: string
|
||||
}) => string
|
||||
} & ({
|
||||
subscribeDomainKey: string
|
||||
} | {
|
||||
subscribeDomain: string
|
||||
}))[] = [{
|
||||
name: "Miniflux",
|
||||
projectUrl: "https://miniflux.app",
|
||||
key: "miniflux",
|
||||
subscribeDomainKey: "minifluxDomain",
|
||||
themeColor: "#33995b",
|
||||
getSubscribePath: (data) => `/bookmarklet?uri=${data.encodedUrl}`
|
||||
}, {
|
||||
name: "Inoreader",
|
||||
projectUrl: "https://www.inoreader.com/",
|
||||
key: "inoreader",
|
||||
subscribeDomainKey: "inoreaderDomain",
|
||||
themeColor: "#0099eb",
|
||||
getSubscribePath: (data) => `/?add_feed=${data.encodedUrl}`
|
||||
}, {
|
||||
name: "Feedly",
|
||||
projectUrl: "https://feedly.com",
|
||||
key: "feedly",
|
||||
subscribeDomain: "https://feedly.com",
|
||||
themeColor: "#2bb24c",
|
||||
getSubscribePath: (data) => `/i/subscription/feed/${data.encodedUrl}`
|
||||
}, {
|
||||
name: "FreshRSS",
|
||||
projectUrl: "https://freshrss.org",
|
||||
key: "freshrss",
|
||||
subscribeDomainKey: "freshrssDomain",
|
||||
themeColor: "#0062db",
|
||||
getSubscribePath: (data) => `/i/?c=feed&a=add&url_rss=${data.encodedUrl}`
|
||||
}, {
|
||||
name: "Tiny Tiny RSS",
|
||||
projectUrl: "https://tt-rss.org/",
|
||||
key: "ttrss",
|
||||
subscribeDomainKey: "ttrssDomain",
|
||||
themeColor: "#f28f34",
|
||||
getSubscribePath: (data) => `/public.php?op=bookmarklets--subscribe&feed_url=${data.encodedUrl}`
|
||||
}, {
|
||||
name: "Nextcloud News",
|
||||
projectUrl: "https://apps.nextcloud.com/apps/news",
|
||||
key: "nextcloudnews",
|
||||
subscribeDomainKey: "nextcloudnewsDomain",
|
||||
themeColor: "#0082c9",
|
||||
getSubscribePath: (data) => `/?subscribe_to=${data.encodedUrl}`
|
||||
}, {
|
||||
name: "Feedbin",
|
||||
projectUrl: "https://feedbin.com/",
|
||||
key: "feedbin",
|
||||
subscribeDomainKey: "feedbinDomain",
|
||||
themeColor: "#0867e2",
|
||||
getSubscribePath: (data) => `/?subscribe=${data.encodedUrl}`
|
||||
}, {
|
||||
name: "The Old Reader",
|
||||
projectUrl: "https://theoldreader.com/",
|
||||
key: "theoldreader",
|
||||
subscribeDomain: "https://theoldreader.com",
|
||||
themeColor: "#ff2300",
|
||||
getSubscribePath: (data) => `/feeds/subscribe?url=${data.encodedUrl}`
|
||||
}, {
|
||||
name: "Feeds.Pub",
|
||||
projectUrl: "https://feeds.pub/",
|
||||
key: "feedspub",
|
||||
subscribeDomain: "https://feeds.pub",
|
||||
themeColor: "#61af4b",
|
||||
getSubscribePath: (data) => `/feed/${data.encodedUrl}`
|
||||
}, {
|
||||
name: "BazQux Reader",
|
||||
projectUrl: "https://bazqux.com/",
|
||||
key: "bazqux",
|
||||
subscribeDomain: "https://bazqux.com",
|
||||
themeColor: "#00af00",
|
||||
getSubscribePath: (data) => `/add?url=${data.encodedUrl}`
|
||||
}, {
|
||||
name: "Qi Reader",
|
||||
projectUrl: "https://www.qireader.com/",
|
||||
key: "qireader",
|
||||
subscribeDomainKey: "qireaderDomain",
|
||||
themeColor: "#e79317",
|
||||
getSubscribePath: (data) => `/discover?search=${data.encodedUrl}`
|
||||
}, {
|
||||
name: "CheckChan",
|
||||
projectUrl: "https://ckc.ftqq.com",
|
||||
key: "checkchan",
|
||||
subscribeDomainKey: "checkchanBase",
|
||||
themeColor: "#f28f34",
|
||||
getSubscribePath: (data) => `/index.html#/check/add?title=${encodeURIComponent(data.title)}&url=${data.encodedUrl}&type=rss&icon=${encodeURIComponent(data.image)}`
|
||||
}, {
|
||||
name: "localReader",
|
||||
key: "local",
|
||||
subscribeDomain: "feed://",
|
||||
themeColor: "#f28f34",
|
||||
getSubscribePath: (data) => data.url.replace(/^https?:\/\//, "")
|
||||
}]
|
||||
} & (
|
||||
| {
|
||||
subscribeDomainKey: string
|
||||
}
|
||||
| {
|
||||
subscribeDomain: string
|
||||
}
|
||||
))[] = [
|
||||
{
|
||||
name: "Miniflux",
|
||||
projectUrl: "https://miniflux.app",
|
||||
key: "miniflux",
|
||||
subscribeDomainKey: "minifluxDomain",
|
||||
themeColor: "#33995b",
|
||||
getSubscribePath: (data) => `/bookmarklet?uri=${data.encodedUrl}`,
|
||||
},
|
||||
{
|
||||
name: "Inoreader",
|
||||
projectUrl: "https://www.inoreader.com/",
|
||||
key: "inoreader",
|
||||
subscribeDomainKey: "inoreaderDomain",
|
||||
themeColor: "#0099eb",
|
||||
getSubscribePath: (data) => `/?add_feed=${data.encodedUrl}`,
|
||||
},
|
||||
{
|
||||
name: "Feedly",
|
||||
projectUrl: "https://feedly.com",
|
||||
key: "feedly",
|
||||
subscribeDomain: "https://feedly.com",
|
||||
themeColor: "#2bb24c",
|
||||
getSubscribePath: (data) => `/i/subscription/feed/${data.encodedUrl}`,
|
||||
},
|
||||
{
|
||||
name: "FreshRSS",
|
||||
projectUrl: "https://freshrss.org",
|
||||
key: "freshrss",
|
||||
subscribeDomainKey: "freshrssDomain",
|
||||
themeColor: "#0062db",
|
||||
getSubscribePath: (data) => `/i/?c=feed&a=add&url_rss=${data.encodedUrl}`,
|
||||
},
|
||||
{
|
||||
name: "Tiny Tiny RSS",
|
||||
projectUrl: "https://tt-rss.org/",
|
||||
key: "ttrss",
|
||||
subscribeDomainKey: "ttrssDomain",
|
||||
themeColor: "#f28f34",
|
||||
getSubscribePath: (data) =>
|
||||
`/public.php?op=bookmarklets--subscribe&feed_url=${data.encodedUrl}`,
|
||||
},
|
||||
{
|
||||
name: "Nextcloud News",
|
||||
projectUrl: "https://apps.nextcloud.com/apps/news",
|
||||
key: "nextcloudnews",
|
||||
subscribeDomainKey: "nextcloudnewsDomain",
|
||||
themeColor: "#0082c9",
|
||||
getSubscribePath: (data) => `/?subscribe_to=${data.encodedUrl}`,
|
||||
},
|
||||
{
|
||||
name: "Feedbin",
|
||||
projectUrl: "https://feedbin.com/",
|
||||
key: "feedbin",
|
||||
subscribeDomainKey: "feedbinDomain",
|
||||
themeColor: "#0867e2",
|
||||
getSubscribePath: (data) => `/?subscribe=${data.encodedUrl}`,
|
||||
},
|
||||
{
|
||||
name: "The Old Reader",
|
||||
projectUrl: "https://theoldreader.com/",
|
||||
key: "theoldreader",
|
||||
subscribeDomain: "https://theoldreader.com",
|
||||
themeColor: "#ff2300",
|
||||
getSubscribePath: (data) => `/feeds/subscribe?url=${data.encodedUrl}`,
|
||||
},
|
||||
{
|
||||
name: "Feeds.Pub",
|
||||
projectUrl: "https://feeds.pub/",
|
||||
key: "feedspub",
|
||||
subscribeDomain: "https://feeds.pub",
|
||||
themeColor: "#61af4b",
|
||||
getSubscribePath: (data) => `/feed/${data.encodedUrl}`,
|
||||
},
|
||||
{
|
||||
name: "BazQux Reader",
|
||||
projectUrl: "https://bazqux.com/",
|
||||
key: "bazqux",
|
||||
subscribeDomain: "https://bazqux.com",
|
||||
themeColor: "#00af00",
|
||||
getSubscribePath: (data) => `/add?url=${data.encodedUrl}`,
|
||||
},
|
||||
{
|
||||
name: "Qi Reader",
|
||||
projectUrl: "https://www.qireader.com/",
|
||||
key: "qireader",
|
||||
subscribeDomainKey: "qireaderDomain",
|
||||
themeColor: "#e79317",
|
||||
getSubscribePath: (data) => `/discover?search=${data.encodedUrl}`,
|
||||
},
|
||||
{
|
||||
name: "CheckChan",
|
||||
projectUrl: "https://ckc.ftqq.com",
|
||||
key: "checkchan",
|
||||
subscribeDomainKey: "checkchanBase",
|
||||
themeColor: "#f28f34",
|
||||
getSubscribePath: (data) =>
|
||||
`/index.html#/check/add?title=${encodeURIComponent(data.title)}&url=${data.encodedUrl}&type=rss&icon=${encodeURIComponent(data.image)}`,
|
||||
},
|
||||
{
|
||||
name: "localReader",
|
||||
key: "local",
|
||||
subscribeDomain: "feed://",
|
||||
themeColor: "#f28f34",
|
||||
getSubscribePath: (data) => data.url.replace(/^https?:\/\//, ""),
|
||||
},
|
||||
]
|
||||
|
||||
+26134
-23297
File diff suppressed because it is too large
Load Diff
+8
-5
@@ -5,7 +5,10 @@ function report({
|
||||
url?: string
|
||||
name?: string
|
||||
}) {
|
||||
if (process.env.PLASMO_PUBLIC_UMAMI_ID && process.env.PLASMO_PUBLIC_UMAMI_URL) {
|
||||
if (
|
||||
process.env.PLASMO_PUBLIC_UMAMI_ID &&
|
||||
process.env.PLASMO_PUBLIC_UMAMI_URL
|
||||
) {
|
||||
let hostname = ""
|
||||
try {
|
||||
hostname = new URL(url).hostname
|
||||
@@ -18,9 +21,9 @@ function report({
|
||||
referrer: hostname,
|
||||
url: hostname,
|
||||
website: process.env.PLASMO_PUBLIC_UMAMI_ID,
|
||||
name: name
|
||||
name: name,
|
||||
},
|
||||
type: "event"
|
||||
type: "event",
|
||||
}
|
||||
|
||||
fetch(`${process.env.PLASMO_PUBLIC_UMAMI_URL}/api/send`, {
|
||||
@@ -29,9 +32,9 @@ function report({
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(umamiData),
|
||||
keepalive: true
|
||||
keepalive: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default report
|
||||
export default report
|
||||
|
||||
+39
-37
@@ -1,10 +1,7 @@
|
||||
import type { RSSData } from "./types"
|
||||
import { fetchRSSContent, parseRSS } from "./utils"
|
||||
|
||||
export async function getPageRSS(data: {
|
||||
html: string
|
||||
url: string
|
||||
}) {
|
||||
export async function getPageRSS(data: { html: string; url: string }) {
|
||||
const parser = new DOMParser()
|
||||
const document = parser.parseFromString(data.html, "text/html")
|
||||
const location = new URL(data.url)
|
||||
@@ -19,12 +16,15 @@ export async function getPageRSS(data: {
|
||||
const image =
|
||||
(document.querySelector('link[rel~="icon"]') &&
|
||||
handleUrl(
|
||||
document.querySelector('link[rel~="icon"]').getAttribute("href")
|
||||
document.querySelector('link[rel~="icon"]').getAttribute("href"),
|
||||
)) ||
|
||||
location.origin + "/favicon.ico"
|
||||
location.origin + "/favicon.ico"
|
||||
|
||||
function handleUrl(url) {
|
||||
return new URL(url.replace(/^(feed:\/\/)/, "https://").replace(/^(feed:)/, ""), location.href).toString()
|
||||
return new URL(
|
||||
url.replace(/^(feed:\/\/)/, "https://").replace(/^(feed:)/, ""),
|
||||
location.href,
|
||||
).toString()
|
||||
}
|
||||
|
||||
let pageRSS: RSSData[] = []
|
||||
@@ -35,7 +35,7 @@ export async function getPageRSS(data: {
|
||||
},
|
||||
check: function (url) {
|
||||
return this.data[url.replace(/^(https?:\/\/|feed:\/\/|feed:)/, "")]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// self rss feed
|
||||
@@ -43,7 +43,7 @@ export async function getPageRSS(data: {
|
||||
if (
|
||||
// Detect RSS without correct Content-Type setting
|
||||
// @ts-ignore
|
||||
document.body.childNodes?.[0]?.tagName?.toLowerCase() === 'pre'
|
||||
document.body.childNodes?.[0]?.tagName?.toLowerCase() === "pre"
|
||||
) {
|
||||
// @ts-ignore
|
||||
html = document.body.childNodes[0].innerText
|
||||
@@ -57,8 +57,8 @@ export async function getPageRSS(data: {
|
||||
pageRSS.push({
|
||||
url: location.href,
|
||||
title: result.title,
|
||||
image
|
||||
});
|
||||
image,
|
||||
})
|
||||
|
||||
// skip the following check if this page is an RSS feed
|
||||
return pageRSS
|
||||
@@ -79,7 +79,7 @@ export async function getPageRSS(data: {
|
||||
"text/rss",
|
||||
"text/atom",
|
||||
"text/rdf",
|
||||
"application/feed+json"
|
||||
"application/feed+json",
|
||||
]
|
||||
const links = document.querySelectorAll("link[type]")
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
@@ -93,7 +93,7 @@ export async function getPageRSS(data: {
|
||||
const feed = {
|
||||
url: handleUrl(feed_url),
|
||||
title: links[i].getAttribute("title") || defaultTitle,
|
||||
image
|
||||
image,
|
||||
}
|
||||
if (!unique.check(feed.url)) {
|
||||
pageRSS.push(feed)
|
||||
@@ -109,7 +109,7 @@ export async function getPageRSS(data: {
|
||||
const feed = {
|
||||
url: handleUrl(ele.getAttribute("href")),
|
||||
title: ele.getAttribute("title") || defaultTitle,
|
||||
image
|
||||
image,
|
||||
}
|
||||
if (!unique.check(feed.url)) {
|
||||
pageRSS.push(feed)
|
||||
@@ -120,7 +120,7 @@ export async function getPageRSS(data: {
|
||||
// normal a
|
||||
const aEles = document.querySelectorAll("a")
|
||||
const check = /([^a-zA-Z]|^)rss([^a-zA-Z]|$)/i
|
||||
const uncertain = [];
|
||||
const uncertain = []
|
||||
for (let i = 0; i < aEles.length; i++) {
|
||||
if (aEles[i].hasAttribute("href")) {
|
||||
const href = aEles[i].getAttribute("href")
|
||||
@@ -147,32 +147,34 @@ export async function getPageRSS(data: {
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all(uncertain.map((feed) => {
|
||||
return new Promise<void>(async (resolve) => {
|
||||
try {
|
||||
const content = await fetchRSSContent(feed.url)
|
||||
const result = parseRSS(content)
|
||||
if (result) {
|
||||
if (result.title) {
|
||||
feed.title = result.title;
|
||||
await Promise.all(
|
||||
uncertain.map((feed) => {
|
||||
return new Promise<void>(async (resolve) => {
|
||||
try {
|
||||
const content = await fetchRSSContent(feed.url)
|
||||
const result = parseRSS(content)
|
||||
if (result) {
|
||||
if (result.title) {
|
||||
feed.title = result.title
|
||||
}
|
||||
pageRSS.push(feed)
|
||||
} else {
|
||||
pageRSS.push({
|
||||
...feed,
|
||||
uncertain: true,
|
||||
})
|
||||
}
|
||||
pageRSS.push(feed);
|
||||
} else {
|
||||
} catch (error) {
|
||||
pageRSS.push({
|
||||
...feed,
|
||||
uncertain: true
|
||||
});
|
||||
uncertain: true,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
pageRSS.push({
|
||||
...feed,
|
||||
uncertain: true
|
||||
});
|
||||
}
|
||||
unique.save(feed.url)
|
||||
resolve()
|
||||
})
|
||||
}))
|
||||
unique.save(feed.url)
|
||||
resolve()
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
return pageRSS
|
||||
}
|
||||
|
||||
+221
-193
@@ -1,213 +1,241 @@
|
||||
import { parse } from 'tldts';
|
||||
import RouteRecognizer from 'route-recognizer';
|
||||
import { parseRules } from './rules';
|
||||
import type { Rule, RSSData } from './types';
|
||||
import RouteRecognizer from "route-recognizer"
|
||||
import { parse } from "tldts"
|
||||
|
||||
import { parseRules } from "./rules"
|
||||
import type { RSSData, Rule } from "./types"
|
||||
|
||||
function ruleHandler(rule: Rule, params, url, html, success, fail) {
|
||||
const run = () => {
|
||||
let resultWithParams;
|
||||
if (typeof rule.target === 'function') {
|
||||
const parser = new DOMParser();
|
||||
const document = parser.parseFromString(html, 'text/html');
|
||||
try {
|
||||
resultWithParams = rule.target(params, url, document);
|
||||
} catch (error) {
|
||||
resultWithParams = '';
|
||||
}
|
||||
} else if (typeof rule.target === 'string') {
|
||||
resultWithParams = rule.target;
|
||||
}
|
||||
|
||||
if (resultWithParams) {
|
||||
// if no :param in resultWithParams, requiredParams will be null
|
||||
// in that case, just skip the following steps and return resultWithParams
|
||||
const requiredParams = resultWithParams.match(/\/:\w+\??(?=\/|$)/g)?.map((param) => ({
|
||||
name: param.slice(2).replace(/\?$/, ''),
|
||||
optional: param.endsWith('?'),
|
||||
}));
|
||||
if (!requiredParams) {
|
||||
return resultWithParams;
|
||||
}
|
||||
for (const param of requiredParams) {
|
||||
if (params[param.name]) {
|
||||
// successfully matched
|
||||
const regex = new RegExp(`/:${param.name}\\??(?=/|$)`);
|
||||
resultWithParams = resultWithParams.replace(regex, `/${params[param.name]}`);
|
||||
} else if (param.optional) {
|
||||
// missing optional parameter, drop all following parameters, otherwise the route will be invalid
|
||||
const regex = new RegExp(`/:${param.name}\\?(/.*)?$`);
|
||||
resultWithParams = resultWithParams.replace(regex, '');
|
||||
break;
|
||||
} else {
|
||||
// missing necessary parameter, fail
|
||||
resultWithParams = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
// bypassing double-check since `:` maybe a part of parameter value
|
||||
// if (resultWithParams && resultWithParams.includes(':')) {
|
||||
// // double-check
|
||||
// resultWithParams = '';
|
||||
// }
|
||||
}
|
||||
return resultWithParams;
|
||||
};
|
||||
const resultWithParams = run();
|
||||
if (resultWithParams) {
|
||||
success(resultWithParams);
|
||||
} else {
|
||||
fail();
|
||||
const run = () => {
|
||||
let resultWithParams
|
||||
if (typeof rule.target === "function") {
|
||||
const parser = new DOMParser()
|
||||
const document = parser.parseFromString(html, "text/html")
|
||||
try {
|
||||
resultWithParams = rule.target(params, url, document)
|
||||
} catch (error) {
|
||||
resultWithParams = ""
|
||||
}
|
||||
} else if (typeof rule.target === "string") {
|
||||
resultWithParams = rule.target
|
||||
}
|
||||
|
||||
if (resultWithParams) {
|
||||
// if no :param in resultWithParams, requiredParams will be null
|
||||
// in that case, just skip the following steps and return resultWithParams
|
||||
const requiredParams = resultWithParams
|
||||
.match(/\/:\w+\??(?=\/|$)/g)
|
||||
?.map((param) => ({
|
||||
name: param.slice(2).replace(/\?$/, ""),
|
||||
optional: param.endsWith("?"),
|
||||
}))
|
||||
if (!requiredParams) {
|
||||
return resultWithParams
|
||||
}
|
||||
for (const param of requiredParams) {
|
||||
if (params[param.name]) {
|
||||
// successfully matched
|
||||
const regex = new RegExp(`/:${param.name}\\??(?=/|$)`)
|
||||
resultWithParams = resultWithParams.replace(
|
||||
regex,
|
||||
`/${params[param.name]}`,
|
||||
)
|
||||
} else if (param.optional) {
|
||||
// missing optional parameter, drop all following parameters, otherwise the route will be invalid
|
||||
const regex = new RegExp(`/:${param.name}\\?(/.*)?$`)
|
||||
resultWithParams = resultWithParams.replace(regex, "")
|
||||
break
|
||||
} else {
|
||||
// missing necessary parameter, fail
|
||||
resultWithParams = ""
|
||||
break
|
||||
}
|
||||
}
|
||||
// bypassing double-check since `:` maybe a part of parameter value
|
||||
// if (resultWithParams && resultWithParams.includes(':')) {
|
||||
// // double-check
|
||||
// resultWithParams = '';
|
||||
// }
|
||||
}
|
||||
return resultWithParams
|
||||
}
|
||||
const resultWithParams = run()
|
||||
if (resultWithParams) {
|
||||
success(resultWithParams)
|
||||
} else {
|
||||
fail()
|
||||
}
|
||||
}
|
||||
|
||||
function formatBlank(str1, str2) {
|
||||
if (str1 && str2) {
|
||||
return str1 + (str1[str1.length - 1].match(/[a-zA-Z0-9]/) || str2[0].match(/[a-zA-Z0-9]/) ? ' ' : '') + str2;
|
||||
} else {
|
||||
return (str1 || '') + (str2 || '');
|
||||
}
|
||||
if (str1 && str2) {
|
||||
return (
|
||||
str1 +
|
||||
(str1[str1.length - 1].match(/[a-zA-Z0-9]/) ||
|
||||
str2[0].match(/[a-zA-Z0-9]/)
|
||||
? " "
|
||||
: "") +
|
||||
str2
|
||||
)
|
||||
} else {
|
||||
return (str1 || "") + (str2 || "")
|
||||
}
|
||||
}
|
||||
|
||||
export function getPageRSSHub(data: {
|
||||
url: string;
|
||||
html: string;
|
||||
rules: string;
|
||||
url: string
|
||||
html: string
|
||||
rules: string
|
||||
}) {
|
||||
const { url, html } = data;
|
||||
const rules = parseRules(data.rules);
|
||||
const { url, html } = data
|
||||
const rules = parseRules(data.rules)
|
||||
|
||||
let parsedDomain;
|
||||
try {
|
||||
parsedDomain = parse(new URL(url).hostname);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
if (parsedDomain && parsedDomain.domain) {
|
||||
const subdomain = parsedDomain.subdomain;
|
||||
const domain = parsedDomain.domain;
|
||||
if (rules[domain]) {
|
||||
let rule = rules[domain][subdomain || '.'] as Rule[];
|
||||
if (!rule) {
|
||||
if (subdomain === 'www') {
|
||||
rule = rules[domain]['.'] as Rule[];
|
||||
} else if (!subdomain) {
|
||||
rule = rules[domain].www as Rule[];
|
||||
}
|
||||
}
|
||||
if (rule) {
|
||||
const recognized = [];
|
||||
rule.forEach((ru, index) => {
|
||||
const oriSources = Object.prototype.toString.call(ru.source) === '[object Array]' ? ru.source : typeof ru.source === 'string' ? [ru.source] : [];
|
||||
let sources = [];
|
||||
// route-recognizer do not support optional segments or partial matching
|
||||
// thus, we need to manually handle it
|
||||
// allowing partial matching is necessary, since many rule authors did not mark optional segments
|
||||
oriSources.forEach((source) => {
|
||||
// trimming `?` is necessary, since route-recognizer considers it as a part of segment
|
||||
source = source.replace(/(\/:\w+)\?(?=\/|$)/g, '$1');
|
||||
sources.push(source);
|
||||
let tailMatch;
|
||||
do {
|
||||
tailMatch = source.match(/\/:\w+$/);
|
||||
if (tailMatch) {
|
||||
const tail = tailMatch[0];
|
||||
source = source.slice(0, source.length - tail.length);
|
||||
sources.push(source);
|
||||
}
|
||||
} while (tailMatch);
|
||||
});
|
||||
// deduplicate (some rule authors may already have done similar job)
|
||||
sources = sources.filter((item, index) => sources.indexOf(item) === index);
|
||||
// match!
|
||||
sources.forEach((source) => {
|
||||
const router = new RouteRecognizer();
|
||||
router.add([
|
||||
{
|
||||
path: source,
|
||||
handler: index,
|
||||
},
|
||||
]);
|
||||
const result = router.recognize(new URL(url).pathname.replace(/\/$/, ''));
|
||||
if (result && result[0]) {
|
||||
recognized.push(result[0]);
|
||||
}
|
||||
});
|
||||
});
|
||||
const result: RSSData[] = [];
|
||||
Promise.all(
|
||||
recognized.map(
|
||||
(recog) =>
|
||||
new Promise<void>((resolve) => {
|
||||
ruleHandler(
|
||||
rule[recog.handler],
|
||||
recog.params,
|
||||
url,
|
||||
html,
|
||||
(parsed) => {
|
||||
if (parsed) {
|
||||
result.push({
|
||||
title: formatBlank(rules[domain]._name ? 'Current' : '', rule[recog.handler].title),
|
||||
url: '{rsshubDomain}' + parsed,
|
||||
path: parsed,
|
||||
});
|
||||
} else {
|
||||
result.push({
|
||||
title: formatBlank(rules[domain]._name ? 'Current' : '', rule[recog.handler].title),
|
||||
url: rule[recog.handler].docs,
|
||||
isDocs: true,
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
() => {
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
return result;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
return [];
|
||||
let parsedDomain
|
||||
try {
|
||||
parsedDomain = parse(new URL(url).hostname)
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
if (parsedDomain && parsedDomain.domain) {
|
||||
const subdomain = parsedDomain.subdomain
|
||||
const domain = parsedDomain.domain
|
||||
if (rules[domain]) {
|
||||
let rule = rules[domain][subdomain || "."] as Rule[]
|
||||
if (!rule) {
|
||||
if (subdomain === "www") {
|
||||
rule = rules[domain]["."] as Rule[]
|
||||
} else if (!subdomain) {
|
||||
rule = rules[domain].www as Rule[]
|
||||
}
|
||||
}
|
||||
if (rule) {
|
||||
const recognized = []
|
||||
rule.forEach((ru, index) => {
|
||||
const oriSources =
|
||||
Object.prototype.toString.call(ru.source) === "[object Array]"
|
||||
? ru.source
|
||||
: typeof ru.source === "string"
|
||||
? [ru.source]
|
||||
: []
|
||||
let sources = []
|
||||
// route-recognizer do not support optional segments or partial matching
|
||||
// thus, we need to manually handle it
|
||||
// allowing partial matching is necessary, since many rule authors did not mark optional segments
|
||||
oriSources.forEach((source) => {
|
||||
// trimming `?` is necessary, since route-recognizer considers it as a part of segment
|
||||
source = source.replace(/(\/:\w+)\?(?=\/|$)/g, "$1")
|
||||
sources.push(source)
|
||||
let tailMatch
|
||||
do {
|
||||
tailMatch = source.match(/\/:\w+$/)
|
||||
if (tailMatch) {
|
||||
const tail = tailMatch[0]
|
||||
source = source.slice(0, source.length - tail.length)
|
||||
sources.push(source)
|
||||
}
|
||||
} while (tailMatch)
|
||||
})
|
||||
// deduplicate (some rule authors may already have done similar job)
|
||||
sources = sources.filter(
|
||||
(item, index) => sources.indexOf(item) === index,
|
||||
)
|
||||
// match!
|
||||
sources.forEach((source) => {
|
||||
const router = new RouteRecognizer()
|
||||
router.add([
|
||||
{
|
||||
path: source,
|
||||
handler: index,
|
||||
},
|
||||
])
|
||||
const result = router.recognize(
|
||||
new URL(url).pathname.replace(/\/$/, ""),
|
||||
)
|
||||
if (result && result[0]) {
|
||||
recognized.push(result[0])
|
||||
}
|
||||
})
|
||||
})
|
||||
const result: RSSData[] = []
|
||||
Promise.all(
|
||||
recognized.map(
|
||||
(recog) =>
|
||||
new Promise<void>((resolve) => {
|
||||
ruleHandler(
|
||||
rule[recog.handler],
|
||||
recog.params,
|
||||
url,
|
||||
html,
|
||||
(parsed) => {
|
||||
if (parsed) {
|
||||
result.push({
|
||||
title: formatBlank(
|
||||
rules[domain]._name ? "Current" : "",
|
||||
rule[recog.handler].title,
|
||||
),
|
||||
url: "{rsshubDomain}" + parsed,
|
||||
path: parsed,
|
||||
})
|
||||
} else {
|
||||
result.push({
|
||||
title: formatBlank(
|
||||
rules[domain]._name ? "Current" : "",
|
||||
rule[recog.handler].title,
|
||||
),
|
||||
url: rule[recog.handler].docs,
|
||||
isDocs: true,
|
||||
})
|
||||
}
|
||||
resolve()
|
||||
},
|
||||
() => {
|
||||
resolve()
|
||||
},
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
return result
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
} else {
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export function getWebsiteRSSHub(data: {
|
||||
url: string;
|
||||
rules: string;
|
||||
}) {
|
||||
const { url } = data;
|
||||
const rules = parseRules(data.rules);
|
||||
let parsedDomain;
|
||||
try {
|
||||
parsedDomain = parse(new URL(url).hostname);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
if (parsedDomain && parsedDomain.domain) {
|
||||
const domain = parsedDomain.domain;
|
||||
if (rules[domain]) {
|
||||
const domainRules = [];
|
||||
for (const subdomainRules in rules[domain]) {
|
||||
if (subdomainRules[0] !== '_') {
|
||||
domainRules.push(...rules[domain][subdomainRules]);
|
||||
}
|
||||
}
|
||||
return domainRules.map((rule) => ({
|
||||
title: formatBlank(rules[domain]._name, rule.title),
|
||||
url: rule.docs,
|
||||
isDocs: true,
|
||||
}) as RSSData);
|
||||
} else {
|
||||
return [];
|
||||
export function getWebsiteRSSHub(data: { url: string; rules: string }) {
|
||||
const { url } = data
|
||||
const rules = parseRules(data.rules)
|
||||
let parsedDomain
|
||||
try {
|
||||
parsedDomain = parse(new URL(url).hostname)
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
if (parsedDomain && parsedDomain.domain) {
|
||||
const domain = parsedDomain.domain
|
||||
if (rules[domain]) {
|
||||
const domainRules = []
|
||||
for (const subdomainRules in rules[domain]) {
|
||||
if (subdomainRules[0] !== "_") {
|
||||
domainRules.push(...rules[domain][subdomainRules])
|
||||
}
|
||||
}
|
||||
return domainRules.map(
|
||||
(rule) =>
|
||||
({
|
||||
title: formatBlank(rules[domain]._name, rule.title),
|
||||
url: rule.docs,
|
||||
isDocs: true,
|
||||
}) as RSSData,
|
||||
)
|
||||
} else {
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
+42
-42
@@ -1,52 +1,52 @@
|
||||
import _ from 'lodash';
|
||||
import { defaultRules } from './radar-rules';
|
||||
import { defaultConfig, getConfig } from './config';
|
||||
import type { Rules } from './types';
|
||||
import _ from "lodash"
|
||||
|
||||
import { defaultConfig, getConfig } from "./config"
|
||||
import { defaultRules } from "./radar-rules"
|
||||
import type { Rules } from "./types"
|
||||
|
||||
export function parseRules(rules: string, forceJSON?: boolean) {
|
||||
let incomeRules = rules;
|
||||
if (incomeRules) {
|
||||
if (typeof rules === 'string') {
|
||||
if (defaultConfig.enableFullRemoteRules && !forceJSON) {
|
||||
incomeRules = window['lave'.split('').reverse().join('')](rules);
|
||||
} else {
|
||||
try {
|
||||
incomeRules = JSON.parse(rules);
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
let incomeRules = rules
|
||||
if (incomeRules) {
|
||||
if (typeof rules === "string") {
|
||||
if (defaultConfig.enableFullRemoteRules && !forceJSON) {
|
||||
incomeRules = window["lave".split("").reverse().join("")](rules)
|
||||
} else {
|
||||
try {
|
||||
incomeRules = JSON.parse(rules)
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
return _.mergeWith(defaultRules, incomeRules, (objValue, srcValue) => {
|
||||
if (_.isFunction(srcValue)) {
|
||||
return srcValue;
|
||||
} else if (_.isFunction(objValue)) {
|
||||
return objValue;
|
||||
}
|
||||
}) as Rules;
|
||||
}
|
||||
return _.mergeWith(defaultRules, incomeRules, (objValue, srcValue) => {
|
||||
if (_.isFunction(srcValue)) {
|
||||
return srcValue
|
||||
} else if (_.isFunction(objValue)) {
|
||||
return objValue
|
||||
}
|
||||
}) as Rules
|
||||
}
|
||||
|
||||
export function getRulesCount(rules: Rules) {
|
||||
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
|
||||
}
|
||||
})
|
||||
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
|
||||
}
|
||||
})
|
||||
return index
|
||||
})
|
||||
return index
|
||||
}
|
||||
|
||||
export function getRemoteRules() {
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
const config = await getConfig()
|
||||
try {
|
||||
const res = await fetch(config.remoteRulesUrl)
|
||||
resolve(res.text())
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
});
|
||||
}
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
const config = await getConfig()
|
||||
try {
|
||||
const res = await fetch(config.remoteRulesUrl)
|
||||
resolve(res.text())
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@
|
||||
--ring: 24.6 95% 53.1%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
.dark {
|
||||
--background: 20 14.3% 4.1%;
|
||||
--foreground: 60 9.1% 97.8%;
|
||||
@@ -48,7 +48,7 @@
|
||||
--ring: 20.5 90.2% 48.2%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
@@ -60,4 +60,4 @@
|
||||
.content a {
|
||||
@apply underline text-orange-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-16
@@ -1,22 +1,22 @@
|
||||
export type Rule = {
|
||||
title: string;
|
||||
docs: string;
|
||||
source: string[];
|
||||
target: string | ((params: any, url: string, document: Document) => string);
|
||||
};
|
||||
title: string
|
||||
docs: string
|
||||
source: string[]
|
||||
target: string | ((params: any, url: string, document: Document) => string)
|
||||
}
|
||||
|
||||
export type Rules = {
|
||||
[domain: string]: {
|
||||
_name: string;
|
||||
[subdomain: string]: Rule[] | string;
|
||||
};
|
||||
};
|
||||
_name: string
|
||||
[subdomain: string]: Rule[] | string
|
||||
}
|
||||
}
|
||||
|
||||
export type RSSData = {
|
||||
url: string;
|
||||
title: string;
|
||||
image?: string;
|
||||
path?: string;
|
||||
isDocs?: boolean;
|
||||
uncertain?: boolean;
|
||||
}
|
||||
url: string
|
||||
title: string
|
||||
image?: string
|
||||
path?: string
|
||||
isDocs?: boolean
|
||||
uncertain?: boolean
|
||||
}
|
||||
|
||||
+14
-9
@@ -1,24 +1,29 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import he from "he"
|
||||
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function removeFunctionFields(obj) {
|
||||
for (var key in obj) {
|
||||
if (typeof obj[key] === 'function') {
|
||||
delete obj[key];
|
||||
} else if (typeof obj[key] === 'object') {
|
||||
removeFunctionFields(obj[key]);
|
||||
if (typeof obj[key] === "function") {
|
||||
delete obj[key]
|
||||
} else if (typeof obj[key] === "object") {
|
||||
removeFunctionFields(obj[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
export function parseRSS(content: string) {
|
||||
if (content.includes("<rss ") || content.includes("<feed ")) {
|
||||
const titleContent = content.match(/<title>(.*)<\/title>/)?.[1]
|
||||
const title = titleContent ? he.decode(titleContent)?.replace(/<!\[CDATA\[(.*)]]>/, (match, p1) => p1)?.trim() : ""
|
||||
const title = titleContent
|
||||
? he
|
||||
.decode(titleContent)
|
||||
?.replace(/<!\[CDATA\[(.*)]]>/, (match, p1) => p1)
|
||||
?.trim()
|
||||
: ""
|
||||
return {
|
||||
title,
|
||||
}
|
||||
@@ -35,4 +40,4 @@ export async function fetchRSSContent(url: string) {
|
||||
// TODO
|
||||
}
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
+39
-20
@@ -1,36 +1,50 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import RSSHubIcon from "data-base64:~/assets/icon.png"
|
||||
import { Link, useLocation } from "react-router-dom"
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
import info from "../../package.json"
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
function Siderbar() {
|
||||
const location = useLocation();
|
||||
const links = [{
|
||||
path: "/",
|
||||
icon: "i-mingcute-settings-3-line",
|
||||
text: "general",
|
||||
}, {
|
||||
path: "/rules",
|
||||
icon: "i-mingcute-list-check-2-line",
|
||||
text: "rules",
|
||||
}, {
|
||||
path: "/about",
|
||||
icon: "i-mingcute-emoji-2-line",
|
||||
text: "about",
|
||||
}]
|
||||
const location = useLocation()
|
||||
const links = [
|
||||
{
|
||||
path: "/",
|
||||
icon: "i-mingcute-settings-3-line",
|
||||
text: "general",
|
||||
},
|
||||
{
|
||||
path: "/rules",
|
||||
icon: "i-mingcute-list-check-2-line",
|
||||
text: "rules",
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
icon: "i-mingcute-emoji-2-line",
|
||||
text: "about",
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-[calc(100vh-80px)] sticky top-10">
|
||||
<div>
|
||||
<div className="px-4 flex items-center space-x-2 text-orange-500 text-xl font-bold mb-8">
|
||||
<img className="w-10 h-10" src={RSSHubIcon} /><span>RSSHub Radar</span>
|
||||
<img className="w-10 h-10" src={RSSHubIcon} />
|
||||
<span>RSSHub Radar</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="w-56 text-lg space-y-2 flex-1">
|
||||
{links.map((link) => (
|
||||
<li key={link.path}>
|
||||
<Link to={link.path} className={cn(location.pathname === link.path ? "bg-orange-50 text-orange-500" : "", "px-4 py-3 hover:bg-orange-50 transition-colors rounded-lg flex items-center space-x-2")}>
|
||||
<Link
|
||||
to={link.path}
|
||||
className={cn(
|
||||
location.pathname === link.path
|
||||
? "bg-orange-50 text-orange-500"
|
||||
: "",
|
||||
"px-4 py-3 hover:bg-orange-50 transition-colors rounded-lg flex items-center space-x-2",
|
||||
)}
|
||||
>
|
||||
<i className={link.icon + " w-5 h-5"}></i>
|
||||
<span>{chrome.i18n.getMessage(link.text)}</span>
|
||||
</Link>
|
||||
@@ -39,7 +53,12 @@ function Siderbar() {
|
||||
</ul>
|
||||
<footer className="text-zinc-500 text-center text-sm">
|
||||
<p>Version v{info.version}</p>
|
||||
<p>Made with <span className="text-red-500">♥</span> by <a className="underline text-orange-500" href="https://diygod.cc/">DIYgod</a></p>
|
||||
<p>
|
||||
Made with <span className="text-red-500">♥</span> by{" "}
|
||||
<a className="underline text-orange-500" href="https://diygod.cc/">
|
||||
DIYgod
|
||||
</a>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { MemoryRouter } from "react-router-dom"
|
||||
|
||||
import { Routing } from "~/options/routes"
|
||||
|
||||
import "~/lib/style.css"
|
||||
import Siderbar from "./Siderbar"
|
||||
|
||||
import { Toaster } from "react-hot-toast"
|
||||
|
||||
import Siderbar from "./Siderbar"
|
||||
|
||||
function Options() {
|
||||
return (
|
||||
<div className="max-w-screen-lg mx-auto text-base py-10">
|
||||
|
||||
@@ -1,37 +1,72 @@
|
||||
import { useEffect } from "react"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
} from "~/lib/components/Card"
|
||||
|
||||
import { Card, CardContent } from "~/lib/components/Card"
|
||||
import report from "~/lib/report"
|
||||
|
||||
function About() {
|
||||
useEffect(() => {
|
||||
report({
|
||||
name: "options-about"
|
||||
name: "options-about",
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-3xl font-medium leading-10 mb-6 text-orange-500 border-b pb-4">{chrome.i18n.getMessage("about")}</h1>
|
||||
<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 text-zinc-700 py-10">
|
||||
<p dangerouslySetInnerHTML={{
|
||||
__html: chrome.i18n.getMessage("RSSHubRadarInfo")
|
||||
}}></p>
|
||||
<p dangerouslySetInnerHTML={{
|
||||
__html: chrome.i18n.getMessage("sponsoredDevelopment")
|
||||
}}></p>
|
||||
<p>{chrome.i18n.getMessage("updateLog")}: <a target="_blank" href="https://github.com/DIYgod/RSSHub-Radar/releases">https://github.com/DIYgod/RSSHub-Radar/releases</a></p>
|
||||
<p>{chrome.i18n.getMessage('questionFeedback')}: <a target="_blank" href="https://github.com/DIYgod/RSSHub-radar/issues">https://github.com/DIYgod/RSSHub-radar/issues</a></p>
|
||||
<p>🐱 GitHub: <a target="_blank" href="https://github.com/DIYgod/RSSHub-Radar">https://github.com/DIYgod/RSSHub-Radar</a></p>
|
||||
<p dangerouslySetInnerHTML={{
|
||||
__html: chrome.i18n.getMessage("rsshubDocument")
|
||||
}}></p>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: chrome.i18n.getMessage("RSSHubRadarInfo"),
|
||||
}}
|
||||
></p>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: chrome.i18n.getMessage("sponsoredDevelopment"),
|
||||
}}
|
||||
></p>
|
||||
<p>
|
||||
{chrome.i18n.getMessage("updateLog")}:{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://github.com/DIYgod/RSSHub-Radar/releases"
|
||||
>
|
||||
https://github.com/DIYgod/RSSHub-Radar/releases
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
{chrome.i18n.getMessage("questionFeedback")}:{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://github.com/DIYgod/RSSHub-radar/issues"
|
||||
>
|
||||
https://github.com/DIYgod/RSSHub-radar/issues
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
🐱 GitHub:{" "}
|
||||
<a target="_blank" href="https://github.com/DIYgod/RSSHub-Radar">
|
||||
https://github.com/DIYgod/RSSHub-Radar
|
||||
</a>
|
||||
</p>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: chrome.i18n.getMessage("rsshubDocument"),
|
||||
}}
|
||||
></p>
|
||||
<p> </p>
|
||||
<p>Made with <span className="text-red-500">♥</span> by <a className="underline text-orange-500" href="https://diygod.cc/">DIYgod</a></p>
|
||||
<p>
|
||||
Made with <span className="text-red-500">♥</span> by{" "}
|
||||
<a
|
||||
className="underline text-orange-500"
|
||||
href="https://diygod.cc/"
|
||||
>
|
||||
DIYgod
|
||||
</a>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -39,4 +74,4 @@ function About() {
|
||||
)
|
||||
}
|
||||
|
||||
export { About }
|
||||
export { About }
|
||||
|
||||
+132
-67
@@ -1,24 +1,21 @@
|
||||
import _ from "lodash"
|
||||
import { Loader2 } from "lucide-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import toast from "react-hot-toast"
|
||||
|
||||
import { sendToBackground } from "@plasmohq/messaging"
|
||||
import { useStorage } from "@plasmohq/storage/hook"
|
||||
|
||||
import { Button } from "~/lib/components/Button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "~/lib/components/Card"
|
||||
import { Input } from "~/lib/components/Input"
|
||||
import { Label } from "~/lib/components/Label"
|
||||
import { Button } from "~/lib/components/Button"
|
||||
import { defaultConfig, setConfig } from "~/lib/config"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/lib/components/Card"
|
||||
import { Switch } from "~/lib/components/Switch"
|
||||
import { defaultConfig, setConfig } from "~/lib/config"
|
||||
import { quickSubscriptions } from "~/lib/quick-subscriptions"
|
||||
import { useStorage } from "@plasmohq/storage/hook"
|
||||
import _ from 'lodash';
|
||||
import { useEffect, useState } from "react"
|
||||
import { parseRules, getRulesCount } from "~/lib/rules"
|
||||
import type { Rules as IRules } from "~/lib/types"
|
||||
import { sendToBackground } from "@plasmohq/messaging"
|
||||
import { Loader2 } from "lucide-react"
|
||||
import toast from "react-hot-toast"
|
||||
import report from "~/lib/report"
|
||||
import { getRulesCount, parseRules } from "~/lib/rules"
|
||||
import type { Rules as IRules } from "~/lib/types"
|
||||
|
||||
function General() {
|
||||
let [config] = useStorage("config")
|
||||
@@ -26,10 +23,10 @@ function General() {
|
||||
const [rules, setRules] = useState<IRules>({})
|
||||
useEffect(() => {
|
||||
sendToBackground({
|
||||
name: "requestDisplayedRules"
|
||||
name: "requestDisplayedRules",
|
||||
}).then((res) => setRules(parseRules(res, true)))
|
||||
report({
|
||||
name: "options-general"
|
||||
name: "options-general",
|
||||
})
|
||||
}, [])
|
||||
|
||||
@@ -42,7 +39,9 @@ function General() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-3xl font-medium leading-10 mb-6 text-orange-500 border-b pb-4">{chrome.i18n.getMessage("general")}</h1>
|
||||
<h1 className="text-3xl font-medium leading-10 mb-6 text-orange-500 border-b pb-4">
|
||||
{chrome.i18n.getMessage("general")}
|
||||
</h1>
|
||||
<div className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -50,76 +49,122 @@ function General() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="notificationsAndReminders">{chrome.i18n.getMessage("notificationsAndReminders")}</Label>
|
||||
<Switch id="notificationsAndReminders" checked={config.notice.badge} onCheckedChange={(value) => setConfig({
|
||||
notice: {
|
||||
badge: value
|
||||
<Label htmlFor="notificationsAndReminders">
|
||||
{chrome.i18n.getMessage("notificationsAndReminders")}
|
||||
</Label>
|
||||
<Switch
|
||||
id="notificationsAndReminders"
|
||||
checked={config.notice.badge}
|
||||
onCheckedChange={(value) =>
|
||||
setConfig({
|
||||
notice: {
|
||||
badge: value,
|
||||
},
|
||||
})
|
||||
}
|
||||
})} />
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label>{chrome.i18n.getMessage("popupWindowHotKey")}</Label>
|
||||
<Button variant="secondary" onClick={() => {
|
||||
chrome.tabs.create({
|
||||
url: 'chrome://extensions/shortcuts'
|
||||
});
|
||||
}}>{chrome.i18n.getMessage("clickToSet")}</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
chrome.tabs.create({
|
||||
url: "chrome://extensions/shortcuts",
|
||||
})
|
||||
}}
|
||||
>
|
||||
{chrome.i18n.getMessage("clickToSet")}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{chrome.i18n.getMessage("RSSHubRelatedSettings")}</CardTitle>
|
||||
<CardTitle>
|
||||
{chrome.i18n.getMessage("RSSHubRelatedSettings")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="customRSSHubDomain">{chrome.i18n.getMessage("customRSSHubDomain")}</Label>
|
||||
<Input id="customRSSHubDomain" value={config.rsshubDomain} onChange={(e) => setConfig({
|
||||
rsshubDomain: e.target.value
|
||||
})} />
|
||||
<Label htmlFor="customRSSHubDomain">
|
||||
{chrome.i18n.getMessage("customRSSHubDomain")}
|
||||
</Label>
|
||||
<Input
|
||||
id="customRSSHubDomain"
|
||||
value={config.rsshubDomain}
|
||||
onChange={(e) =>
|
||||
setConfig({
|
||||
rsshubDomain: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="accessKey" className="flex items-center">
|
||||
<span>{chrome.i18n.getMessage("accessKey")}</span>
|
||||
<a className="h-[14px] ml-1" target="_blank" href="https://docs.rsshub.app/install/config#access-keycode">
|
||||
<a
|
||||
className="h-[14px] ml-1"
|
||||
target="_blank"
|
||||
href="https://docs.rsshub.app/install/config#access-keycode"
|
||||
>
|
||||
<i className="i-mingcute-question-line"></i>
|
||||
</a>
|
||||
</Label>
|
||||
<Input
|
||||
type="password"
|
||||
id="accessKey"
|
||||
value={config.rsshubAccessControl.accessKey} onChange={(e) => setConfig({
|
||||
rsshubAccessControl: {
|
||||
accessKey: e.target.value,
|
||||
}
|
||||
})}
|
||||
placeholder={chrome.i18n.getMessage("configurationRequiredIfAccessKeysEnabled")}
|
||||
value={config.rsshubAccessControl.accessKey}
|
||||
onChange={(e) =>
|
||||
setConfig({
|
||||
rsshubAccessControl: {
|
||||
accessKey: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
placeholder={chrome.i18n.getMessage(
|
||||
"configurationRequiredIfAccessKeysEnabled",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="remoteRulesUrl">{chrome.i18n.getMessage("remoteRulesUrl")}</Label>
|
||||
<Input id="remoteRulesUrl" value={config.remoteRulesUrl} onChange={(e) => setConfig({
|
||||
remoteRulesUrl: e.target.value
|
||||
})} />
|
||||
<Label htmlFor="remoteRulesUrl">
|
||||
{chrome.i18n.getMessage("remoteRulesUrl")}
|
||||
</Label>
|
||||
<Input
|
||||
id="remoteRulesUrl"
|
||||
value={config.remoteRulesUrl}
|
||||
onChange={(e) =>
|
||||
setConfig({
|
||||
remoteRulesUrl: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label>{chrome.i18n.getMessage("manuallyUpdate")}</Label>
|
||||
<p className="text-zinc-500 text-sm">{chrome.i18n.getMessage("totalNumberOfRules")}: {count}</p>
|
||||
<p className="text-zinc-500 text-sm">{chrome.i18n.getMessage("updateTip")}</p>
|
||||
<p className="text-zinc-500 text-sm">
|
||||
{chrome.i18n.getMessage("totalNumberOfRules")}: {count}
|
||||
</p>
|
||||
<p className="text-zinc-500 text-sm">
|
||||
{chrome.i18n.getMessage("updateTip")}
|
||||
</p>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={rulesUpdating}
|
||||
onClick={() => {
|
||||
setRulesUpdating(true)
|
||||
sendToBackground({
|
||||
name: "refreshRules"
|
||||
name: "refreshRules",
|
||||
}).then((res) => {
|
||||
setRulesUpdating(false)
|
||||
toast.success(chrome.i18n.getMessage("updateSuccessful"))
|
||||
})
|
||||
}}
|
||||
>
|
||||
{rulesUpdating && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{rulesUpdating && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
)}
|
||||
{chrome.i18n.getMessage("updateNow")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -131,30 +176,50 @@ function General() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{quickSubscriptions.map((quickSubscription) => (
|
||||
<div className="flex items-center space-x-2 h-10" key={quickSubscription.key}>
|
||||
<Switch id={quickSubscription.key} checked={config.submitto[quickSubscription.key]} onCheckedChange={(value) => setConfig({
|
||||
submitto: {
|
||||
[quickSubscription.key]: value
|
||||
<div
|
||||
className="flex items-center space-x-2 h-10"
|
||||
key={quickSubscription.key}
|
||||
>
|
||||
<Switch
|
||||
id={quickSubscription.key}
|
||||
checked={config.submitto[quickSubscription.key]}
|
||||
onCheckedChange={(value) =>
|
||||
setConfig({
|
||||
submitto: {
|
||||
[quickSubscription.key]: value,
|
||||
},
|
||||
} as any)
|
||||
}
|
||||
} as any)} />
|
||||
<Label className="w-28" htmlFor={quickSubscription.key}>{chrome.i18n.getMessage(quickSubscription.name) || quickSubscription.name}</Label>
|
||||
{config.submitto[quickSubscription.key] && (
|
||||
"subscribeDomainKey" in quickSubscription ? (
|
||||
/>
|
||||
<Label className="w-28" htmlFor={quickSubscription.key}>
|
||||
{chrome.i18n.getMessage(quickSubscription.name) ||
|
||||
quickSubscription.name}
|
||||
</Label>
|
||||
{config.submitto[quickSubscription.key] &&
|
||||
("subscribeDomainKey" in quickSubscription ? (
|
||||
<Input
|
||||
className="flex-1"
|
||||
id={quickSubscription.subscribeDomainKey}
|
||||
value={config.submitto[quickSubscription.subscribeDomainKey]}
|
||||
onChange={(e) => setConfig({
|
||||
submitto: {
|
||||
[quickSubscription.subscribeDomainKey]: e.target.value
|
||||
}
|
||||
} as any)}
|
||||
value={
|
||||
config.submitto[quickSubscription.subscribeDomainKey]
|
||||
}
|
||||
onChange={(e) =>
|
||||
setConfig({
|
||||
submitto: {
|
||||
[quickSubscription.subscribeDomainKey]:
|
||||
e.target.value,
|
||||
},
|
||||
} as any)
|
||||
}
|
||||
placeholder={chrome.i18n.getMessage("fillInstanceDomain")}
|
||||
/>
|
||||
) : (
|
||||
<Input className="flex-1" disabled value={quickSubscription.subscribeDomain}/>
|
||||
)
|
||||
)}
|
||||
<Input
|
||||
className="flex-1"
|
||||
disabled
|
||||
value={quickSubscription.subscribeDomain}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
@@ -164,4 +229,4 @@ function General() {
|
||||
)
|
||||
}
|
||||
|
||||
export { General }
|
||||
export { General }
|
||||
|
||||
@@ -7,21 +7,21 @@ import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger
|
||||
AccordionTrigger,
|
||||
} from "~/lib/components/Accordion"
|
||||
import { Card, CardContent } from "~/lib/components/Card"
|
||||
import { parseRules, getRulesCount } from "~/lib/rules"
|
||||
import type { Rules as IRules } from "~/lib/types"
|
||||
import report from "~/lib/report"
|
||||
import { getRulesCount, parseRules } from "~/lib/rules"
|
||||
import type { Rules as IRules } from "~/lib/types"
|
||||
|
||||
function Rules() {
|
||||
const [rules, setRules] = useState<IRules>({})
|
||||
useEffect(() => {
|
||||
sendToBackground({
|
||||
name: "requestDisplayedRules"
|
||||
name: "requestDisplayedRules",
|
||||
}).then((res) => setRules(parseRules(res, true)))
|
||||
report({
|
||||
name: "options-rules"
|
||||
name: "options-rules",
|
||||
})
|
||||
}, [])
|
||||
|
||||
@@ -36,10 +36,14 @@ function Rules() {
|
||||
{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>
|
||||
<p>
|
||||
{chrome.i18n.getMessage("totalNumberOfRules")}: {count}
|
||||
</p>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: chrome.i18n.getMessage("forMoreRulesJoinUs"),
|
||||
}}
|
||||
></p>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<Card>
|
||||
@@ -73,7 +77,7 @@ function Rules() {
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Route, Routes } from "react-router-dom"
|
||||
|
||||
import { About } from "./About"
|
||||
import { Rules } from "./Rules"
|
||||
import { General } from "./General"
|
||||
import { Rules } from "./Rules"
|
||||
|
||||
export const Routing = () => (
|
||||
<Routes>
|
||||
@@ -10,4 +10,4 @@ export const Routing = () => (
|
||||
<Route path="/rules" element={<Rules />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
</Routes>
|
||||
)
|
||||
)
|
||||
|
||||
+44
-31
@@ -1,13 +1,13 @@
|
||||
import RSSHubIcon from "data-base64:~/assets/icon.png"
|
||||
import MD5 from "md5.js"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useCopyToClipboard } from "usehooks-ts"
|
||||
|
||||
import { Button } from "~/lib/components/Button"
|
||||
import { defaultConfig, getConfig } from "~/lib/config"
|
||||
import type { RSSData } from "~/lib/types"
|
||||
import RSSHubIcon from "data-base64:~/assets/icon.png"
|
||||
import { useCopyToClipboard } from 'usehooks-ts'
|
||||
import MD5 from 'md5.js';
|
||||
import { quickSubscriptions } from "~/lib/quick-subscriptions"
|
||||
import report from "~/lib/report"
|
||||
import type { RSSData } from "~/lib/types"
|
||||
|
||||
function RSSItem({
|
||||
item,
|
||||
@@ -30,15 +30,21 @@ function RSSItem({
|
||||
}
|
||||
}, [copied])
|
||||
|
||||
let url = item.url.replace('{rsshubDomain}', config.rsshubDomain.replace(/\/$/, ''));
|
||||
if (type === 'currentPageRSSHub' && config.rsshubAccessControl.accessKey) {
|
||||
url = `${url}?code=${new MD5().update(item.path + config.rsshubAccessControl.accessKey).digest('hex')}`
|
||||
let url = item.url.replace(
|
||||
"{rsshubDomain}",
|
||||
config.rsshubDomain.replace(/\/$/, ""),
|
||||
)
|
||||
if (type === "currentPageRSSHub" && config.rsshubAccessControl.accessKey) {
|
||||
url = `${url}?code=${new MD5().update(item.path + config.rsshubAccessControl.accessKey).digest("hex")}`
|
||||
}
|
||||
if (type === 'currentPageRSSHub') {
|
||||
item.title = item.title.replace(/^Current/, chrome.i18n.getMessage('current'));
|
||||
if (type === "currentPageRSSHub") {
|
||||
item.title = item.title.replace(
|
||||
/^Current/,
|
||||
chrome.i18n.getMessage("current"),
|
||||
)
|
||||
}
|
||||
url = encodeURI(url);
|
||||
const encodedUrl = encodeURIComponent(url);
|
||||
url = encodeURI(url)
|
||||
const encodedUrl = encodeURIComponent(url)
|
||||
|
||||
return (
|
||||
<li className="flex mb-4 items-center space-x-2 w-max min-w-full">
|
||||
@@ -46,7 +52,8 @@ function RSSItem({
|
||||
<a
|
||||
target="_blank"
|
||||
href={url}
|
||||
className="w-48 cursor-pointer flex flex-col justify-between text-black flex-1">
|
||||
className="w-48 cursor-pointer flex flex-col justify-between text-black flex-1"
|
||||
>
|
||||
<span className="text-[13px] truncate">{item.title}</span>
|
||||
<span className="text-xs truncate text-zinc-400">
|
||||
{url.replace("https://", "").replace("http://", "")}
|
||||
@@ -58,7 +65,7 @@ function RSSItem({
|
||||
size="sm"
|
||||
onClickCapture={() => {
|
||||
report({
|
||||
name: "popup-docs"
|
||||
name: "popup-docs",
|
||||
})
|
||||
}}
|
||||
>
|
||||
@@ -68,13 +75,18 @@ function RSSItem({
|
||||
</Button>
|
||||
)}
|
||||
{!item.isDocs && (
|
||||
<Button variant="rss" size="sm" className="w-[60px]" onClick={() => {
|
||||
copy(url)
|
||||
setCopied(true)
|
||||
report({
|
||||
name: "popup-copy"
|
||||
})
|
||||
}}>
|
||||
<Button
|
||||
variant="rss"
|
||||
size="sm"
|
||||
className="w-[60px]"
|
||||
onClick={() => {
|
||||
copy(url)
|
||||
setCopied(true)
|
||||
report({
|
||||
name: "popup-copy",
|
||||
})
|
||||
}}
|
||||
>
|
||||
{chrome.i18n.getMessage(copied ? "copied" : "copy")}
|
||||
</Button>
|
||||
)}
|
||||
@@ -84,24 +96,24 @@ function RSSItem({
|
||||
size="sm"
|
||||
className="border-[#0ea5e9] text-[#0ea5e9] hover:bg-[#0ea5e9]"
|
||||
>
|
||||
<a
|
||||
target="_blank"
|
||||
href={`/tabs/preview.html?url=${encodedUrl}`}
|
||||
>
|
||||
<a target="_blank" href={`/tabs/preview.html?url=${encodedUrl}`}>
|
||||
{chrome.i18n.getMessage("preview")}
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
{quickSubscriptions.map((quickSubscription) => {
|
||||
if (item.isDocs
|
||||
|| !config.submitto[quickSubscription.key]
|
||||
|| ("subscribeDomainKey" in quickSubscription && !config.submitto[quickSubscription.subscribeDomainKey])
|
||||
if (
|
||||
item.isDocs ||
|
||||
!config.submitto[quickSubscription.key] ||
|
||||
("subscribeDomainKey" in quickSubscription &&
|
||||
!config.submitto[quickSubscription.subscribeDomainKey])
|
||||
) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
let subscriptionDomain
|
||||
if ("subscribeDomainKey" in quickSubscription) {
|
||||
subscriptionDomain = config.submitto[quickSubscription.subscribeDomainKey]
|
||||
subscriptionDomain =
|
||||
config.submitto[quickSubscription.subscribeDomainKey]
|
||||
} else {
|
||||
subscriptionDomain = quickSubscription.subscribeDomain
|
||||
}
|
||||
@@ -114,7 +126,7 @@ function RSSItem({
|
||||
className={`border-[${quickSubscription.themeColor}] text-[${quickSubscription.themeColor}] hover:bg-[${quickSubscription.themeColor}]`}
|
||||
onClickCapture={() => {
|
||||
report({
|
||||
name: `popup-subscribe-${quickSubscription.key}`
|
||||
name: `popup-subscribe-${quickSubscription.key}`,
|
||||
})
|
||||
}}
|
||||
key={quickSubscription.key}
|
||||
@@ -128,7 +140,8 @@ function RSSItem({
|
||||
image: item.image,
|
||||
})}`}
|
||||
>
|
||||
{chrome.i18n.getMessage(quickSubscription.name) || quickSubscription.name}
|
||||
{chrome.i18n.getMessage(quickSubscription.name) ||
|
||||
quickSubscription.name}
|
||||
</a>
|
||||
</Button>
|
||||
)
|
||||
|
||||
+6
-11
@@ -1,19 +1,14 @@
|
||||
import RSSItem from './RSSItem'
|
||||
import type { RSSData } from '~/lib/types'
|
||||
import type { RSSData } from "~/lib/types"
|
||||
|
||||
function RSSList({
|
||||
type,
|
||||
list,
|
||||
}: {
|
||||
type: string
|
||||
list: RSSData[]
|
||||
}) {
|
||||
import RSSItem from "./RSSItem"
|
||||
|
||||
function RSSList({ type, list }: { type: string; list: RSSData[] }) {
|
||||
if (list.length === 0) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-base font-bold">{chrome.i18n.getMessage(type)}</h2>
|
||||
<h2 className="text-base font-bold">{chrome.i18n.getMessage(type)}</h2>
|
||||
<ul>
|
||||
{list.map((item) => (
|
||||
<RSSItem key={item.url + item.title} item={item} type={type} />
|
||||
@@ -23,4 +18,4 @@ function RSSList({
|
||||
)
|
||||
}
|
||||
|
||||
export default RSSList
|
||||
export default RSSList
|
||||
|
||||
+45
-23
@@ -1,9 +1,13 @@
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
import "~/lib/style.css"
|
||||
|
||||
import { sendToBackground } from "@plasmohq/messaging"
|
||||
import RSSList from "./RSSList"
|
||||
|
||||
import report from "~/lib/report"
|
||||
|
||||
import RSSList from "./RSSList"
|
||||
|
||||
function IndexPopup() {
|
||||
const [data, setData] = useState({
|
||||
pageRSS: [],
|
||||
@@ -14,36 +18,54 @@ function IndexPopup() {
|
||||
sendToBackground({
|
||||
name: "popupReady",
|
||||
}).then((res) => {
|
||||
setData(Object.assign({
|
||||
pageRSS: [],
|
||||
pageRSSHub: [],
|
||||
websiteRSSHub: [],
|
||||
}, res))
|
||||
setData(
|
||||
Object.assign(
|
||||
{
|
||||
pageRSS: [],
|
||||
pageRSSHub: [],
|
||||
websiteRSSHub: [],
|
||||
},
|
||||
res,
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
chrome.tabs.query({
|
||||
active: true,
|
||||
}, ([tab]) => {
|
||||
report({
|
||||
url: tab.url,
|
||||
name: "popup"
|
||||
})
|
||||
});
|
||||
chrome.tabs.query(
|
||||
{
|
||||
active: true,
|
||||
},
|
||||
([tab]) => {
|
||||
report({
|
||||
url: tab.url,
|
||||
name: "popup",
|
||||
})
|
||||
},
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="min-w-[350px] p-5">
|
||||
<a className="absolute right-4 h-6 flex items-center" href="/options.html" target="_blank">
|
||||
<a
|
||||
className="absolute right-4 h-6 flex items-center"
|
||||
href="/options.html"
|
||||
target="_blank"
|
||||
>
|
||||
<i className="i-mingcute-settings-3-line w-5 h-5 text-slate-600 hover:text-black transition-colors"></i>
|
||||
</a>
|
||||
{!data.pageRSS.length && !data.pageRSSHub.length && !data.websiteRSSHub.length && (
|
||||
<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>
|
||||
)}
|
||||
{!data.pageRSS.length &&
|
||||
!data.pageRSSHub.length &&
|
||||
!data.websiteRSSHub.length && (
|
||||
<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 type="currentPageRSS" list={data.pageRSS} />
|
||||
<RSSList type="currentPageRSSHub" list={data.pageRSSHub} />
|
||||
<RSSList type="currentSiteRSSHub" list={data.websiteRSSHub} />
|
||||
|
||||
+59
-45
@@ -1,9 +1,9 @@
|
||||
import { getPageRSS } from "~/lib/rss"
|
||||
import { getPageRSSHub, getWebsiteRSSHub } from "~/lib/rsshub"
|
||||
import { parseRules } from '~/lib/rules';
|
||||
import { parseRules } from "~/lib/rules"
|
||||
import { removeFunctionFields } from "~/lib/utils"
|
||||
|
||||
export { }
|
||||
export {}
|
||||
|
||||
export const getRSS = async ({
|
||||
html,
|
||||
@@ -48,49 +48,63 @@ export const getDisplayedRules = (rules: string) => {
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("message", (event: MessageEvent<{
|
||||
name: "requestRSS"
|
||||
body: {
|
||||
html: string
|
||||
url: string
|
||||
rules: string
|
||||
tabId: number
|
||||
}
|
||||
} | {
|
||||
name: "requestDisplayedRules"
|
||||
body: {
|
||||
rules: string
|
||||
}
|
||||
}>) => {
|
||||
switch (event.data?.name) {
|
||||
case "requestRSS": {
|
||||
getRSS({
|
||||
html: event.data.body.html,
|
||||
url: event.data.body.url,
|
||||
rules: event.data.body.rules,
|
||||
callback: (rss) => {
|
||||
event.source.postMessage({
|
||||
name: "responseRSS",
|
||||
window.addEventListener(
|
||||
"message",
|
||||
(
|
||||
event: MessageEvent<
|
||||
| {
|
||||
name: "requestRSS"
|
||||
body: {
|
||||
html: string
|
||||
url: string
|
||||
rules: string
|
||||
tabId: number
|
||||
}
|
||||
}
|
||||
| {
|
||||
name: "requestDisplayedRules"
|
||||
body: {
|
||||
rules: string
|
||||
}
|
||||
}
|
||||
>,
|
||||
) => {
|
||||
switch (event.data?.name) {
|
||||
case "requestRSS": {
|
||||
getRSS({
|
||||
html: event.data.body.html,
|
||||
url: event.data.body.url,
|
||||
rules: event.data.body.rules,
|
||||
callback: (rss) => {
|
||||
event.source.postMessage(
|
||||
{
|
||||
name: "responseRSS",
|
||||
body: {
|
||||
url: "url" in event.data.body && event.data.body.url,
|
||||
tabId: "tabId" in event.data.body && event.data.body.tabId,
|
||||
rss,
|
||||
},
|
||||
},
|
||||
event.origin as any,
|
||||
)
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
case "requestDisplayedRules": {
|
||||
const displayedRules = getDisplayedRules(event.data.body.rules)
|
||||
event.source.postMessage(
|
||||
{
|
||||
name: "responseDisplayedRules",
|
||||
body: {
|
||||
url: 'url' in event.data.body && event.data.body.url,
|
||||
tabId: 'tabId' in event.data.body && event.data.body.tabId,
|
||||
rss,
|
||||
displayedRules,
|
||||
},
|
||||
}, event.origin as any)
|
||||
},
|
||||
})
|
||||
break
|
||||
},
|
||||
event.origin as any,
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
case "requestDisplayedRules": {
|
||||
const displayedRules = getDisplayedRules(event.data.body.rules)
|
||||
event.source.postMessage({
|
||||
name: "responseDisplayedRules",
|
||||
body: {
|
||||
displayedRules,
|
||||
},
|
||||
}, event.origin as any)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { useRef } from "react";
|
||||
import { useRef } from "react"
|
||||
|
||||
import { sendToBackground } from "@plasmohq/messaging"
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data?.name.startsWith("response")) {
|
||||
chrome.runtime.sendMessage(event.data)
|
||||
sendToBackground(event.data)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
function OffscreenPage() {
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null)
|
||||
|
||||
chrome.runtime.onMessage.addListener((msg) => {
|
||||
iframeRef.current?.contentWindow?.postMessage(msg.data, "*");
|
||||
iframeRef.current?.contentWindow?.postMessage(msg.data, "*")
|
||||
})
|
||||
|
||||
return (
|
||||
<iframe id="sandbox" src="/sandboxes/index.html" ref={iframeRef}></iframe>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default OffscreenPage;
|
||||
export default OffscreenPage
|
||||
|
||||
+52
-37
@@ -1,8 +1,11 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Parser from "rss-parser";
|
||||
import "~/lib/style.css";
|
||||
import { useEffect, useState } from "react"
|
||||
import Parser from "rss-parser"
|
||||
|
||||
import "~/lib/style.css"
|
||||
|
||||
import RSSHubIcon from "data-base64:~/assets/icon.png"
|
||||
import xss from "xss";
|
||||
import xss from "xss"
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -11,18 +14,21 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/lib/components/Card"
|
||||
import RSSItem from "~/popup/RSSItem";
|
||||
import { fetchRSSContent } from "~/lib/utils"
|
||||
import RSSItem from "~/popup/RSSItem"
|
||||
|
||||
const parser = new Parser();
|
||||
const parser = new Parser()
|
||||
|
||||
function PreviewPage() {
|
||||
const url = new URLSearchParams(window.location.search).get("url");
|
||||
const url = new URLSearchParams(window.location.search).get("url")
|
||||
|
||||
const [parsed, setParsed] = useState<Parser.Output<{
|
||||
[key: string]: any;
|
||||
}> | undefined>();
|
||||
const [error, setError] = useState<Event>();
|
||||
const [parsed, setParsed] = useState<
|
||||
| Parser.Output<{
|
||||
[key: string]: any
|
||||
}>
|
||||
| undefined
|
||||
>()
|
||||
const [error, setError] = useState<Event>()
|
||||
|
||||
useEffect(() => {
|
||||
const run = async () => {
|
||||
@@ -33,8 +39,8 @@ function PreviewPage() {
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
}
|
||||
};
|
||||
run();
|
||||
}
|
||||
run()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
@@ -42,56 +48,65 @@ function PreviewPage() {
|
||||
<div className="flex">
|
||||
{parsed?.image?.url && (
|
||||
<div className="w-24 h-24 overflow-hidden rounded-xl mr-8 object-cover">
|
||||
<img className="object-cover" src={parsed?.image?.url || RSSHubIcon} />
|
||||
<img
|
||||
className="object-cover"
|
||||
src={parsed?.image?.url || RSSHubIcon}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2 flex-1">
|
||||
<h1 className="text-3xl font-bold text-primary">{parsed?.title}</h1>
|
||||
<div className="text-zinc-600">{parsed?.description}</div>
|
||||
<div className="text-zinc-600">
|
||||
<a className="underline" href={parsed?.link}>{parsed?.link}</a>
|
||||
<a className="underline" href={parsed?.link}>
|
||||
{parsed?.link}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-fit flex items-center justify-center">
|
||||
<RSSItem item={{
|
||||
title: "",
|
||||
url,
|
||||
image: RSSHubIcon,
|
||||
}} type="currentPageRSS" hidePreview={true} />
|
||||
<RSSItem
|
||||
item={{
|
||||
title: "",
|
||||
url,
|
||||
image: RSSHubIcon,
|
||||
}}
|
||||
type="currentPageRSS"
|
||||
hidePreview={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-8">
|
||||
{parsed?.items?.map((item, index) => (
|
||||
<Card key={index}>
|
||||
<CardHeader>
|
||||
<CardTitle><a href={item.link}>{item.title}</a></CardTitle>
|
||||
<CardTitle>
|
||||
<a href={item.link}>{item.title}</a>
|
||||
</CardTitle>
|
||||
<CardDescription>{item.pubDate}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-zinc-600 text-sm max-h-96 overflow-y-auto [&_img]:max-h-60 [&_img]:m-auto [&_p]:my-2" dangerouslySetInnerHTML={{
|
||||
__html: xss(item["content:encoded"] || item["content"])
|
||||
}}></div>
|
||||
<div
|
||||
className="text-zinc-600 text-sm max-h-96 overflow-y-auto [&_img]:max-h-60 [&_img]:m-auto [&_p]:my-2"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: xss(item["content:encoded"] || item["content"]),
|
||||
}}
|
||||
></div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="text-zinc-400">
|
||||
Source: <a className="underline break-all" href={item.link}>{item.link}</a>
|
||||
Source:{" "}
|
||||
<a className="underline break-all" href={item.link}>
|
||||
{item.link}
|
||||
</a>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
{error && (
|
||||
<div className="text-red-600">
|
||||
Error: {error.toString()}
|
||||
</div>
|
||||
)}
|
||||
{!parsed && (
|
||||
<div className="text-zinc-400">
|
||||
Loading...
|
||||
</div>
|
||||
)}
|
||||
{error && <div className="text-red-600">Error: {error.toString()}</div>}
|
||||
{!parsed && <div className="text-zinc-400">Loading...</div>}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default PreviewPage;
|
||||
export default PreviewPage
|
||||
|
||||
+4
-6
@@ -1,6 +1,7 @@
|
||||
import { quickSubscriptions } from "./src/lib/quick-subscriptions"
|
||||
import { iconsPlugin } from "@egoist/tailwindcss-icons"
|
||||
|
||||
import { quickSubscriptions } from "./src/lib/quick-subscriptions"
|
||||
|
||||
const safelist = []
|
||||
quickSubscriptions.forEach((subscription) => {
|
||||
if (subscription.themeColor) {
|
||||
@@ -94,8 +95,5 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
iconsPlugin(),
|
||||
require("tailwindcss-animate")
|
||||
],
|
||||
}
|
||||
plugins: [iconsPlugin(), require("tailwindcss-animate")],
|
||||
}
|
||||
|
||||
+5
-13
@@ -1,19 +1,11 @@
|
||||
{
|
||||
"extends": "plasmo/templates/tsconfig.base",
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
".plasmo/index.d.ts",
|
||||
"./**/*.ts",
|
||||
"./**/*.tsx"
|
||||
],
|
||||
"exclude": ["node_modules"],
|
||||
"include": [".plasmo/index.d.ts", "./**/*.ts", "./**/*.tsx"],
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"~/*": [
|
||||
"./src/*"
|
||||
]
|
||||
"~/*": ["./src/*"],
|
||||
},
|
||||
"baseUrl": "."
|
||||
}
|
||||
"baseUrl": ".",
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user