chore: use prettier

This commit is contained in:
DIYgod
2024-02-01 23:03:25 +08:00
parent 7dce94c191
commit 7e926b2462
54 changed files with 32470 additions and 26453 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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
+25
View File
@@ -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
View File
@@ -21,6 +21,6 @@ export default {
"",
"^~(.*)$",
"",
"^[./]"
]
"^[./]",
],
}
+1 -1
View File
@@ -149,4 +149,4 @@
"current": {
"message": "Current"
}
}
}
+1 -1
View File
@@ -149,4 +149,4 @@
"current": {
"message": "当前"
}
}
}
+1 -1
View File
@@ -13,4 +13,4 @@
"components": "~/lib/components",
"utils": "~/lib/utils"
}
}
}
+8 -1
View File
@@ -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": [
+5185 -2295
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -4,6 +4,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}
autoprefixer: {},
},
}
+7 -7
View File
@@ -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,
});
})
}
}
}
+8 -8
View File
@@ -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()
+4 -3
View File
@@ -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
+12 -8
View File
@@ -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
+3 -2
View File
@@ -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
+4 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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,
});
})
}
}
+22 -18
View File
@@ -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)
}
}
}
+2 -1
View File
@@ -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 {}
+4 -3
View File
@@ -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}
>
+6 -6
View File
@@ -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"
+2 -2
View File
@@ -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}
/>
+2 -2
View File
@@ -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"
+4 -3
View File
@@ -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<
+3 -2
View File
@@ -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
View File
@@ -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
}
+3 -5
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+8 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>
)
+5 -1
View File
@@ -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">
+55 -20
View File
@@ -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>&nbsp;</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
View File
@@ -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 }
+14 -10
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
}
})
}
},
)
}
+8 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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": ".",
},
}