mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-02-11 17:20:41 +01:00
alpha release build 0.3.1
Version 0.3.1
This commit is contained in:
commit
331a07fb19
1
dist/styles.css
vendored
1
dist/styles.css
vendored
@ -560,6 +560,7 @@ img.favicon {
|
||||
align-items: center;
|
||||
color: var(--neutralSecondary);
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.info {
|
||||
|
15
package.json
15
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fluent-reader",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"description": "A simplistic, modern desktop RSS reader",
|
||||
"main": "./dist/electron.js",
|
||||
"scripts": {
|
||||
@ -22,7 +22,10 @@
|
||||
"output": "./bin/${platform}/${arch}/"
|
||||
},
|
||||
"win": {
|
||||
"target": [ "nsis", "appx" ]
|
||||
"target": [
|
||||
"nsis",
|
||||
"appx"
|
||||
]
|
||||
},
|
||||
"appx": {
|
||||
"applicationId": "FluentReader",
|
||||
@ -43,7 +46,9 @@
|
||||
"deleteAppDataOnUninstall": true
|
||||
},
|
||||
"mac": {
|
||||
"target": [ "dmg" ]
|
||||
"target": [
|
||||
"dmg"
|
||||
]
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -55,17 +60,14 @@
|
||||
"@types/redux": "^3.6.0",
|
||||
"@types/redux-thunk": "^2.1.0",
|
||||
"@types/reselect": "^2.2.0",
|
||||
"@yang991178/electron-proxy-agent": "^1.2.1",
|
||||
"@yang991178/rss-parser": "^3.8.1",
|
||||
"electron": "^9.0.4",
|
||||
"electron-builder": "^22.7.0",
|
||||
"electron-react-devtools": "^0.5.3",
|
||||
"electron-store": "^5.2.0",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"favicon": "0.0.2",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"nedb": "^1.8.0",
|
||||
"pac-proxy-agent": "^4.1.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-intl-universal": "^2.2.5",
|
||||
@ -74,7 +76,6 @@
|
||||
"redux-devtools": "^3.5.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"simplebar-react": "^2.2.0",
|
||||
"ts-loader": "^7.0.4",
|
||||
"typescript": "^3.9.2",
|
||||
"webpack": "^4.43.0",
|
||||
|
@ -164,6 +164,9 @@ class AppTab extends React.Component<AppTabProps, AppTabState> {
|
||||
text={intl.get("app.setPac")} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<span className="settings-hint up">
|
||||
{intl.get("app.pacHint")}
|
||||
</span>
|
||||
</form>}
|
||||
|
||||
<Label>{intl.get("app.cleanup")}</Label>
|
||||
|
@ -55,7 +55,26 @@ function createWindow() {
|
||||
mainWindow.loadFile((app.isPackaged ? "dist/" : "") + "index.html")
|
||||
}
|
||||
|
||||
Menu.setApplicationMenu(null)
|
||||
if (process.platform === 'darwin') {
|
||||
const template = [
|
||||
{
|
||||
label: "Application",
|
||||
submenu: [
|
||||
{ label: "Quit", accelerator: "Command+Q", click: () => { app.quit() } }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Edit",
|
||||
submenu: [
|
||||
{ label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" },
|
||||
{ label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" },
|
||||
]
|
||||
}
|
||||
]
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||
} else {
|
||||
Menu.setApplicationMenu(null)
|
||||
}
|
||||
|
||||
app.on("ready", createWindow)
|
||||
|
||||
@ -66,6 +85,9 @@ app.on("second-instance", () => {
|
||||
})
|
||||
|
||||
app.on("window-all-closed", function () {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.session.clearStorageData({ storages: ["cookies"] })
|
||||
}
|
||||
mainWindow = null
|
||||
if (restarting) {
|
||||
init()
|
||||
|
@ -141,6 +141,7 @@
|
||||
"enableProxy": "Enable Proxy",
|
||||
"badUrl": "Invalid URL",
|
||||
"pac": "PAC Address",
|
||||
"setPac": "Set PAC"
|
||||
"setPac": "Set PAC",
|
||||
"pacHint": "For Socks proxies, it is recommended for PAC to return \"SOCKS5\" for proxy-side DNS. Turning off proxy requires restart."
|
||||
}
|
||||
}
|
@ -141,6 +141,7 @@
|
||||
"enableProxy": "启用代理",
|
||||
"badUrl": "请正确输入URL",
|
||||
"pac": "PAC地址",
|
||||
"setPac": "设置PAC"
|
||||
"setPac": "设置PAC",
|
||||
"pacHint": "对于Socks代理建议PAC返回“SOCKS5”以启用代理端解析。关闭代理需重启应用后生效。"
|
||||
}
|
||||
}
|
@ -163,7 +163,7 @@ export function fetchItems(): AppThunk<Promise<void>> {
|
||||
if (r.status === "fulfilled") items.push(...r.value)
|
||||
else {
|
||||
console.log(r.reason)
|
||||
dispatch(fetchItemsFailure(getState().sources[i], r.reason))
|
||||
dispatch(fetchItemsFailure(sources[i], r.reason))
|
||||
}
|
||||
})
|
||||
insertItems(items)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Parser = require("@yang991178/rss-parser")
|
||||
import intl = require("react-intl-universal")
|
||||
import * as db from "../db"
|
||||
import { rssParser, faviconPromise, ActionStatus, AppThunk } from "../utils"
|
||||
import { rssParser, fetchFavicon, ActionStatus, AppThunk, parseRSS } from "../utils"
|
||||
import { RSSItem, insertItems, ItemActionTypes, FETCH_ITEMS, MARK_READ, MARK_UNREAD, MARK_ALL_READ } from "./item"
|
||||
import { SourceGroup } from "./group"
|
||||
import { saveSettings } from "./app"
|
||||
@ -29,21 +29,15 @@ export class RSSSource {
|
||||
}
|
||||
|
||||
async fetchMetaData(parser: Parser) {
|
||||
let feed = await parser.parseURL(this.url)
|
||||
let feed = await parseRSS(this.url)
|
||||
if (!this.name) {
|
||||
if (feed.title) this.name = feed.title.trim()
|
||||
this.name = this.name || intl.get("sources.untitled")
|
||||
}
|
||||
let domain = this.url.split("/").slice(0, 3).join("/")
|
||||
let f: string = null
|
||||
try {
|
||||
f = await faviconPromise(domain)
|
||||
if (f === null) f = domain + "/favicon.ico"
|
||||
let result = await fetch(f)
|
||||
if (result.status == 200 && result.headers.has("Content-Type")
|
||||
&& result.headers.get("Content-Type").startsWith("image")) {
|
||||
this.iconurl = f
|
||||
}
|
||||
let f = await fetchFavicon(domain)
|
||||
if (f !== null) this.iconurl = f
|
||||
} finally {
|
||||
return feed
|
||||
}
|
||||
@ -82,7 +76,7 @@ export class RSSSource {
|
||||
}
|
||||
|
||||
static async fetchItems(source: RSSSource, parser: Parser) {
|
||||
let feed = await parser.parseURL(source.url)
|
||||
let feed = await parseRSS(source.url)
|
||||
db.sdb.update({ sid: source.sid }, { $set: { lastFetched: new Date() } })
|
||||
return await this.checkItems(source, feed.items)
|
||||
}
|
||||
|
@ -35,12 +35,11 @@ export function setProxy(address = null) {
|
||||
} else {
|
||||
store.set(PAC_STORE_KEY, address)
|
||||
}
|
||||
remote.getCurrentWebContents().session.setProxy({
|
||||
pacScript: getProxyStatus() ? address : ""
|
||||
})
|
||||
remote.session.fromPartition("sandbox").setProxy({
|
||||
pacScript: getProxyStatus() ? address : ""
|
||||
})
|
||||
if (getProxyStatus()) {
|
||||
let rules = { pacScript: address }
|
||||
remote.getCurrentWebContents().session.setProxy(rules)
|
||||
remote.session.fromPartition("sandbox").setProxy(rules)
|
||||
}
|
||||
}
|
||||
|
||||
const VIEW_STORE_KEY = "view"
|
||||
|
@ -17,41 +17,61 @@ export type AppThunk<ReturnType = void> = ThunkAction<
|
||||
export type AppDispatch = ThunkDispatch<RootState, undefined, AnyAction>
|
||||
|
||||
import Parser = require("@yang991178/rss-parser")
|
||||
const customFields = {
|
||||
item: ["thumb", "image", ["content:encoded", "fullContent"]] as Parser.CustomFieldItem[]
|
||||
}
|
||||
|
||||
import ElectronProxyAgent = require("@yang991178/electron-proxy-agent")
|
||||
import { ViewType } from "./models/page"
|
||||
import { IPartialTheme } from "@fluentui/react"
|
||||
import { SourceGroup } from "./models/group"
|
||||
let agent = new ElectronProxyAgent(remote.getCurrentWebContents().session)
|
||||
export const rssParser = new Parser({
|
||||
customFields: customFields,
|
||||
requestOptions: {
|
||||
agent: agent
|
||||
customFields: {
|
||||
item: ["thumb", "image", ["content:encoded", "fullContent"]] as Parser.CustomFieldItem[]
|
||||
}
|
||||
})
|
||||
|
||||
export async function parseRSS(url: string) {
|
||||
try {
|
||||
let result = await fetch(url, { credentials: "omit" })
|
||||
if (result.ok) {
|
||||
return await rssParser.parseString(await result.text())
|
||||
} else {
|
||||
throw new Error(result.statusText)
|
||||
}
|
||||
} catch {
|
||||
throw new Error("A network error has occured.")
|
||||
}
|
||||
}
|
||||
|
||||
export const domParser = new DOMParser()
|
||||
|
||||
const favicon = require("favicon")
|
||||
export function faviconPromise(url: string): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
favicon(url, (err, icon: string) => {
|
||||
if (err) reject(err)
|
||||
else if (!icon) resolve(icon)
|
||||
else {
|
||||
let parts = icon.split("//")
|
||||
resolve(parts[0] + "//" + parts[parts.length - 1])
|
||||
import Url = require("url")
|
||||
export async function fetchFavicon(url: string) {
|
||||
try {
|
||||
let result = await fetch(url, { credentials: "omit" })
|
||||
if (result.ok) {
|
||||
let html = await result.text()
|
||||
let dom = domParser.parseFromString(html, "text/html")
|
||||
let links = dom.getElementsByTagName("link")
|
||||
for (let link of links) {
|
||||
let rel = link.getAttribute("rel")
|
||||
if ((rel === "icon" || rel === "shortcut icon") && link.hasAttribute("href")) {
|
||||
let href = link.getAttribute("href")
|
||||
let parsedUrl = Url.parse(url)
|
||||
if (href.startsWith("//")) return parsedUrl.protocol + href
|
||||
else if (href.startsWith("/")) return url + href
|
||||
else return href
|
||||
}
|
||||
}
|
||||
}
|
||||
url = url + "/favicon.ico"
|
||||
result = await fetch(url, { credentials: "omit" })
|
||||
if (result.status == 200 && result.headers.has("Content-Type")
|
||||
&& result.headers.get("Content-Type").startsWith("image")) {
|
||||
return url
|
||||
}
|
||||
return null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function htmlDecode(input: string) {
|
||||
var doc = domParser.parseFromString(input, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
var doc = domParser.parseFromString(input, "text/html")
|
||||
return doc.documentElement.textContent
|
||||
}
|
||||
|
||||
export function openExternal(url: string) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user