Merge pull request #339 from yang991178/1.1.0

Version 1.1.0
This commit is contained in:
Haoyuan Liu 2021-12-16 17:28:02 +08:00 committed by GitHub
commit 73930f3e69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 671 additions and 766 deletions

View File

@ -1,10 +1,3 @@
# Build the MAS app
CSC_IDENTITY_AUTO_DISCOVERY=false npx electron-builder -c electron-builder-mas.yml --mac mas:universal
# Add ElectronTeamID to Info.plist
sed -i '' -e 's/<\/dict>/<key>ElectronTeamID<\/key><string>EM8VE646TZ<\/string><\/dict>/g' "bin/darwin/universal/mas-universal/Fluent Reader.app/Contents/Info.plist"
printf "......................\nresignAndPackage start\n\n"
# Name of your app. # Name of your app.
APP="Fluent Reader" APP="Fluent Reader"
# Your Certificate name. # Your Certificate name.
@ -21,6 +14,17 @@ PARENT_PLIST="build/entitlements.mas.plist"
CHILD_PLIST="build/entitlements.mas.inherit.plist" CHILD_PLIST="build/entitlements.mas.inherit.plist"
LOGINHELPER_PLIST="build/entitlements.mas.loginhelper.plist" LOGINHELPER_PLIST="build/entitlements.mas.loginhelper.plist"
FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks"
# Build universal binary for font-list
# FONTLIST_PATH="node_modules/font-list/libs/darwin/fontlist.m"
# clang -arch arm64 -arch x86_64 "$FONTLIST_PATH" -fmodules -o "dist/fontlist"
# Build the MAS app
CSC_IDENTITY_AUTO_DISCOVERY=false npx electron-builder -c electron-builder-mas.yml --mac mas:universal
# Add ElectronTeamID to Info.plist
sed -i '' -e 's/<\/dict>/<key>ElectronTeamID<\/key><string>EM8VE646TZ<\/string><\/dict>/g' "bin/darwin/universal/mas-universal/Fluent Reader.app/Contents/Info.plist"
printf "......................\nresignAndPackage start\n\n"
codesign --deep --force --verify --verbose=4 --timestamp --options runtime --entitlements "$CHILD_PLIST" -s "$APP_KEY" "$APP_PATH/Contents/Resources/app.asar.unpacked/dist/fontlist"
codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Electron Framework" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Electron Framework"
codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib"
codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libGLESv2.dylib" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libGLESv2.dylib"

View File

@ -2,14 +2,25 @@
html, html,
body { body {
margin: 0;
font-family: "Segoe UI", "Source Han Sans SC Regular", "Microsoft YaHei", font-family: "Segoe UI", "Source Han Sans SC Regular", "Microsoft YaHei",
sans-serif; sans-serif;
} }
html { body {
padding: 12px 96px 32px;
overflow: hidden scroll; overflow: hidden scroll;
} }
body.rtl {
direction: rtl;
}
body.vertical {
padding: 32px;
padding-right: 96px;
writing-mode: vertical-rl;
overflow: scroll hidden;
}
:root { :root {
margin: 12px 96px 32px;
--gray: #484644; --gray: #484644;
--primary: #0078d4; --primary: #0078d4;
--primary-alt: #004578; --primary-alt: #004578;
@ -58,6 +69,11 @@ a:active {
margin: 0 auto; margin: 0 auto;
display: none; display: none;
} }
body.vertical #main {
max-width: unset;
max-height: 700px;
margin: auto 0;
}
#main.show { #main.show {
display: block; display: block;
animation-name: fadeIn; animation-name: fadeIn;
@ -80,12 +96,21 @@ a:active {
article { article {
line-height: 1.6; line-height: 1.6;
} }
body.vertical article {
line-height: 1.5;
}
body.vertical article p {
text-indent: 2rem;
}
article * { article * {
max-width: 100%; max-width: 100%;
} }
article img { article img {
height: auto; height: auto;
} }
body.vertical article img {
max-height: 75%;
}
article figure { article figure {
margin: 16px 0; margin: 16px 0;
text-align: center; text-align: center;
@ -103,6 +128,11 @@ article code {
font-size: 0.875rem; font-size: 0.875rem;
line-height: 1; line-height: 1;
} }
article pre {
word-break: normal;
overflow-wrap: normal;
white-space: pre-wrap;
}
article blockquote { article blockquote {
border-left: 2px solid var(--gray); border-left: 2px solid var(--gray);
margin: 1em 0; margin: 1em 0;

View File

@ -3,14 +3,14 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" <meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src-elem 'sha256-sLDWrq1tUAO8IyyqmUckFqxbXYfZ2/3TEUmtxH8Unf0=' 'sha256-9YXu4Ifpt+hDzuBhE+vFtXKt1ZRbo/CkuUY4VX4dZyE='; img-src http: https: data:; style-src 'self' 'unsafe-inline'; frame-src http: https:; media-src http: https:; connect-src https: http:"> content="default-src 'none'; script-src-elem 'sha256-sLDWrq1tUAO8IyyqmUckFqxbXYfZ2/3TEUmtxH8Unf0=' 'sha256-iOdZeo0zvgcSuiH/7/dXCOHo7s0cn2XtsidqVOcHBjo='; img-src http: https: data:; style-src 'self' 'unsafe-inline'; frame-src http: https:; media-src http: https:; connect-src https: http:">
<title>Article</title> <title>Article</title>
<link rel="stylesheet" href="article.css" /> <link rel="stylesheet" href="article.css" />
<script integrity="sha256-sLDWrq1tUAO8IyyqmUckFqxbXYfZ2/3TEUmtxH8Unf0=" src="mercury.web.js"></script> <script integrity="sha256-sLDWrq1tUAO8IyyqmUckFqxbXYfZ2/3TEUmtxH8Unf0=" src="mercury.web.js"></script>
</head> </head>
<body> <body>
<div id="main"></div> <div id="main"></div>
<script integrity="sha256-9YXu4Ifpt+hDzuBhE+vFtXKt1ZRbo/CkuUY4VX4dZyE=" src="article.js"></script> <script integrity="sha256-iOdZeo0zvgcSuiH/7/dXCOHo7s0cn2XtsidqVOcHBjo=" src="article.js"></script>
<!-- Run "cat article.js | openssl dgst -sha256 -binary | openssl enc -base64 -A" for hash --> <!-- Run "cat article.js | openssl dgst -sha256 -binary | openssl enc -base64 -A" for hash -->
</body> </body>
</html> </html>

View File

@ -2,6 +2,15 @@ function get(name) {
if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search)) if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search))
return decodeURIComponent(name[1]); return decodeURIComponent(name[1]);
} }
let dir = get("d")
if (dir === "1") {
document.body.classList.add("rtl")
} else if (dir === "2") {
document.body.classList.add("vertical")
document.body.addEventListener("wheel", (evt) => {
document.scrollingElement.scrollLeft -= evt.deltaY;
});
}
async function getArticle(url) { async function getArticle(url) {
let article = get("a") let article = get("a")
if (get("m") === "1") { if (get("m") === "1") {
@ -11,6 +20,8 @@ async function getArticle(url) {
} }
} }
document.documentElement.style.fontSize = get("s") + "px" document.documentElement.style.fontSize = get("s") + "px"
let font = get("f")
if (font) document.body.style.fontFamily = `"${font}"`
let url = get("u") let url = get("u")
getArticle(url).then(article => { getArticle(url).then(article => {
let domParser = new DOMParser() let domParser = new DOMParser()
@ -32,4 +43,3 @@ getArticle(url).then(article => {
main.innerHTML = dom.body.innerHTML main.innerHTML = dom.body.innerHTML
main.classList.add("show") main.classList.add("show")
}) })

BIN
dist/fontlist vendored Executable file

Binary file not shown.

29
dist/fonts.vbs vendored Normal file
View File

@ -0,0 +1,29 @@
Option Explicit
Dim objShell, objFSO, objFile, objFolder
Dim objFolderItem, colItems, objFont
Dim strFileName
Const FONTS = &H14& ' Fonts Folder
' Instantiate Objects
Set objShell = CreateObject("Shell.Application")
Set objFolder = objShell.Namespace(FONTS)
Set objFolderItem = objFolder.Self
Set colItems = objFolder.Items
Set objFSO = CreateObject("Scripting.FileSystemObject")
For Each objFont in colItems
WScript.StdOut.WriteLine(objFont.Path & vbtab & objFont.Name)
Next
Set objShell = nothing
Set objFile = nothing
Set objFolder = nothing
Set objFolderItem = nothing
Set colItems = nothing
Set objFont = nothing
Set objFSO = nothing
wscript.quit

View File

@ -138,6 +138,10 @@ i.ms-Nav-chevron {
color: var(--neutralPrimary); color: var(--neutralPrimary);
} }
.ms-Callout-main {
border-radius: 5px;
}
#root > nav { #root > nav {
height: var(--navHeight); height: var(--navHeight);
-webkit-app-region: drag; -webkit-app-region: drag;

View File

@ -4,7 +4,10 @@ productName: Fluent Reader
copyright: Copyright © 2020 Haoyuan Liu copyright: Copyright © 2020 Haoyuan Liu
files: files:
- "./dist/**/*" - "./dist/**/*"
- "!./dist/fonts.vbs"
- "!**/*.js.map" - "!**/*.js.map"
asarUnpack:
- "./dist/fontlist"
directories: directories:
output: "./bin/${platform}/${arch}/" output: "./bin/${platform}/${arch}/"
mac: mac:
@ -25,6 +28,7 @@ mac:
- uk - uk
- it - it
- nl - nl
- ko
minimumSystemVersion: 10.14.0 minimumSystemVersion: 10.14.0
mas: mas:
entitlements: build/entitlements.mas.plist entitlements: build/entitlements.mas.plist

View File

@ -3,6 +3,7 @@ productName: Fluent Reader
copyright: Copyright © 2020 Haoyuan Liu copyright: Copyright © 2020 Haoyuan Liu
files: files:
- "./dist/**/*" - "./dist/**/*"
- "!./dist/fontlist"
- "!**/*.js.map" - "!**/*.js.map"
directories: directories:
output: "./bin/${platform}/${arch}/" output: "./bin/${platform}/${arch}/"
@ -24,6 +25,7 @@ mac:
- uk - uk
- it - it
- nl - nl
- ko
win: win:
target: target:
- nsis - nsis
@ -46,6 +48,7 @@ appx:
- uk - uk
- it - it
- nl - nl
- ko
showNameOnTiles: true showNameOnTiles: true
setBuildNumber: true setBuildNumber: true
nsis: nsis:

1094
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "fluent-reader", "name": "fluent-reader",
"version": "1.0.2", "version": "1.1.0",
"description": "Modern desktop RSS reader", "description": "Modern desktop RSS reader",
"main": "./dist/electron.js", "main": "./dist/electron.js",
"scripts": { "scripts": {
@ -26,11 +26,12 @@
"@types/react-dom": "^16.9.8", "@types/react-dom": "^16.9.8",
"@types/react-redux": "^7.1.9", "@types/react-redux": "^7.1.9",
"@yang991178/rss-parser": "^3.8.1", "@yang991178/rss-parser": "^3.8.1",
"electron": "^13.1.4", "electron": "^16.0.2",
"electron-builder": "^22.11.3", "electron-builder": "^22.11.3",
"electron-react-devtools": "^0.5.3", "electron-react-devtools": "^0.5.3",
"electron-store": "^5.2.0", "electron-store": "^5.2.0",
"electron-window-state": "^5.0.3", "electron-window-state": "^5.0.3",
"font-list": "^1.4.2",
"hard-source-webpack-plugin": "^0.13.1", "hard-source-webpack-plugin": "^0.13.1",
"html-webpack-plugin": "^4.3.0", "html-webpack-plugin": "^4.3.0",
"js-md5": "^0.7.3", "js-md5": "^0.7.3",

View File

@ -75,6 +75,13 @@ const settingsBridge = {
ipcRenderer.invoke("set-font-size", size) ipcRenderer.invoke("set-font-size", size)
}, },
getFont: (): string => {
return ipcRenderer.sendSync("get-font")
},
setFont: (font: string) => {
ipcRenderer.invoke("set-font", font)
},
getFetchInterval: (): number => { getFetchInterval: (): number => {
return ipcRenderer.sendSync("get-fetch-interval") return ipcRenderer.sendSync("get-fetch-interval")
}, },

View File

@ -170,11 +170,16 @@ const utilsBridge = {
destroyTouchBar: () => { destroyTouchBar: () => {
ipcRenderer.invoke("touchbar-destroy") ipcRenderer.invoke("touchbar-destroy")
}, },
initFontList: (): Promise<Array<string>> => {
return ipcRenderer.invoke("init-font-list")
},
} }
declare global { declare global {
interface Window { interface Window {
utils: typeof utilsBridge utils: typeof utilsBridge
fontList: Array<string>
} }
} }

View File

@ -12,7 +12,11 @@ import {
Icon, Icon,
Link, Link,
} from "@fluentui/react" } from "@fluentui/react"
import { RSSSource, SourceOpenTarget } from "../scripts/models/source" import {
RSSSource,
SourceOpenTarget,
SourceTextDirection,
} from "../scripts/models/source"
import { shareSubmenu } from "./context-menu" import { shareSubmenu } from "./context-menu"
import { platformCtrl, decodeFetchResponse } from "../scripts/utils" import { platformCtrl, decodeFetchResponse } from "../scripts/utils"
@ -31,9 +35,14 @@ type ArticleProps = {
textMenu: (position: [number, number], text: string, url: string) => void textMenu: (position: [number, number], text: string, url: string) => void
imageMenu: (position: [number, number]) => void imageMenu: (position: [number, number]) => void
dismissContextMenu: () => void dismissContextMenu: () => void
updateSourceTextDirection: (
source: RSSSource,
direction: SourceTextDirection
) => void
} }
type ArticleState = { type ArticleState = {
fontFamily: string
fontSize: number fontSize: number
loadWebpage: boolean loadWebpage: boolean
loadFull: boolean loadFull: boolean
@ -49,7 +58,8 @@ class Article extends React.Component<ArticleProps, ArticleState> {
constructor(props: ArticleProps) { constructor(props: ArticleProps) {
super(props) super(props)
this.state = { this.state = {
fontSize: this.getFontSize(), fontFamily: window.settings.getFont(),
fontSize: window.settings.getFontSize(),
loadWebpage: props.source.openTarget === SourceOpenTarget.Webpage, loadWebpage: props.source.openTarget === SourceOpenTarget.Webpage,
loadFull: props.source.openTarget === SourceOpenTarget.FullContent, loadFull: props.source.openTarget === SourceOpenTarget.FullContent,
fullContent: "", fullContent: "",
@ -64,15 +74,16 @@ class Article extends React.Component<ArticleProps, ArticleState> {
this.loadFull() this.loadFull()
} }
getFontSize = () => {
return window.settings.getFontSize()
}
setFontSize = (size: number) => { setFontSize = (size: number) => {
window.settings.setFontSize(size) window.settings.setFontSize(size)
this.setState({ fontSize: size }) this.setState({ fontSize: size })
} }
setFont = (font: string) => {
window.settings.setFont(font)
this.setState({ fontFamily: font })
}
fontMenuProps = (): IContextualMenuProps => ({ fontSizeMenuProps = (): IContextualMenuProps => ({
items: FONT_SIZE_OPTIONS.map(size => ({ items: FONT_SIZE_OPTIONS.map(size => ({
key: String(size), key: String(size),
text: String(size), text: String(size),
@ -82,6 +93,53 @@ class Article extends React.Component<ArticleProps, ArticleState> {
})), })),
}) })
fontFamilyMenuProps = (): IContextualMenuProps => ({
items: window.fontList.map((font, idx) => ({
key: String(idx),
text: font === "" ? intl.get("default") : font,
canCheck: true,
checked: this.state.fontFamily === font,
onClick: () => this.setFont(font),
})),
})
updateTextDirection = (direction: SourceTextDirection) => {
this.props.updateSourceTextDirection(this.props.source, direction)
}
directionMenuProps = (): IContextualMenuProps => ({
items: [
{
key: "LTR",
text: intl.get("article.LTR"),
iconProps: { iconName: "Forward" },
canCheck: true,
checked: this.props.source.textDir === SourceTextDirection.LTR,
onClick: () =>
this.updateTextDirection(SourceTextDirection.LTR),
},
{
key: "RTL",
text: intl.get("article.RTL"),
iconProps: { iconName: "Back" },
canCheck: true,
checked: this.props.source.textDir === SourceTextDirection.RTL,
onClick: () =>
this.updateTextDirection(SourceTextDirection.RTL),
},
{
key: "Vertical",
text: intl.get("article.Vertical"),
iconProps: { iconName: "Down" },
canCheck: true,
checked:
this.props.source.textDir === SourceTextDirection.Vertical,
onClick: () =>
this.updateTextDirection(SourceTextDirection.Vertical),
},
],
})
moreMenuProps = (): IContextualMenuProps => ({ moreMenuProps = (): IContextualMenuProps => ({
items: [ items: [
{ {
@ -117,10 +175,24 @@ class Article extends React.Component<ArticleProps, ArticleState> {
}, },
{ {
key: "fontMenu", key: "fontMenu",
text: intl.get("article.font"),
iconProps: { iconName: "Font" },
disabled: this.state.loadWebpage,
subMenuProps: this.fontFamilyMenuProps(),
},
{
key: "fontSizeMenu",
text: intl.get("article.fontSize"), text: intl.get("article.fontSize"),
iconProps: { iconName: "FontSize" }, iconProps: { iconName: "FontSize" },
disabled: this.state.loadWebpage, disabled: this.state.loadWebpage,
subMenuProps: this.fontMenuProps(), subMenuProps: this.fontSizeMenuProps(),
},
{
key: "directionMenu",
text: intl.get("article.textDir"),
iconProps: { iconName: "ChangeEntitlements" },
disabled: this.state.loadWebpage,
subMenuProps: this.directionMenuProps(),
}, },
{ {
key: "divider_1", key: "divider_1",
@ -290,7 +362,9 @@ class Article extends React.Component<ArticleProps, ArticleState> {
</> </>
) )
) )
return `article/article.html?a=${a}&h=${h}&s=${this.state.fontSize}&u=${ return `article/article.html?a=${a}&h=${h}&f=${encodeURIComponent(
this.state.fontFamily
)}&s=${this.state.fontSize}&d=${this.props.source.textDir}&u=${
this.props.item.link this.props.item.link
}&m=${this.state.loadFull ? 1 : 0}` }&m=${this.state.loadFull ? 1 : 0}`
} }
@ -400,7 +474,7 @@ class Article extends React.Component<ArticleProps, ArticleState> {
? this.props.item.link ? this.props.item.link
: this.articleView() : this.articleView()
} }
webpreferences="contextIsolation,disableDialogs,autoplayPolicy=document-user-activation-required" webpreferences="contextIsolation,disableDialogs,autoplayPolicy=document-user-activation-required,nativeWindowOpen=false"
partition={this.state.loadWebpage ? "sandbox" : undefined} partition={this.state.loadWebpage ? "sandbox" : undefined}
/> />
)} )}

View File

@ -20,7 +20,6 @@ import {
DefaultButton, DefaultButton,
ChoiceGroup, ChoiceGroup,
IChoiceGroupOption, IChoiceGroupOption,
loadTheme,
Dropdown, Dropdown,
IDropdownOption, IDropdownOption,
PrimaryButton, PrimaryButton,
@ -140,6 +139,7 @@ class AppTab extends React.Component<AppTabProps, AppTabState> {
{ key: "sv", text: "Svenska" }, { key: "sv", text: "Svenska" },
{ key: "tr", text: "Türkçe" }, { key: "tr", text: "Türkçe" },
{ key: "uk", text: "Українська" }, { key: "uk", text: "Українська" },
{ key: "ko", text: "한글" },
{ key: "ja", text: "日本語" }, { key: "ja", text: "日本語" },
{ key: "zh-CN", text: "中文(简体)" }, { key: "zh-CN", text: "中文(简体)" },
{ key: "zh-TW", text: "中文(繁體)" }, { key: "zh-TW", text: "中文(繁體)" },

View File

@ -17,6 +17,11 @@ import {
closeContextMenu, closeContextMenu,
openImageMenu, openImageMenu,
} from "../scripts/models/app" } from "../scripts/models/app"
import {
RSSSource,
SourceTextDirection,
updateSource,
} from "../scripts/models/source"
type ArticleContainerProps = { type ArticleContainerProps = {
itemId: number itemId: number
@ -58,6 +63,14 @@ const mapDispatchToProps = (dispatch: AppDispatch) => {
imageMenu: (position: [number, number]) => imageMenu: (position: [number, number]) =>
dispatch(openImageMenu(position)), dispatch(openImageMenu(position)),
dismissContextMenu: () => dispatch(closeContextMenu()), dismissContextMenu: () => dispatch(closeContextMenu()),
updateSourceTextDirection: (
source: RSSSource,
direction: SourceTextDirection
) => {
dispatch(
updateSource({ ...source, textDir: direction } as RSSSource)
)
},
} }
} }

View File

@ -26,6 +26,11 @@ window.utils.addMainContextListener((pos, text) => {
store.dispatch(openTextMenu(pos, text)) store.dispatch(openTextMenu(pos, text))
}) })
window.fontList = [""]
window.utils.initFontList().then(fonts => {
window.fontList.push(...fonts)
})
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <Provider store={store}>
<Root /> <Root />

View File

@ -122,6 +122,14 @@ ipcMain.handle("set-font-size", (_, size: number) => {
store.set(FONT_SIZE_STORE_KEY, size) store.set(FONT_SIZE_STORE_KEY, size)
}) })
const FONT_STORE_KEY = "fontFamily"
ipcMain.on("get-font", event => {
event.returnValue = store.get(FONT_STORE_KEY, "")
})
ipcMain.handle("set-font", (_, font: string) => {
store.set(FONT_STORE_KEY, font)
})
ipcMain.on("get-all-settings", event => { ipcMain.on("get-all-settings", event => {
let output = {} let output = {}
for (let [key, value] of store) { for (let [key, value] of store) {

View File

@ -1,16 +1,9 @@
import { import { ipcMain, shell, dialog, app, session, clipboard } from "electron"
ipcMain,
shell,
dialog,
app,
session,
clipboard,
TouchBar,
} from "electron"
import { WindowManager } from "./window" import { WindowManager } from "./window"
import fs = require("fs") import fs = require("fs")
import { ImageCallbackTypes, TouchBarTexts } from "../schema-types" import { ImageCallbackTypes, TouchBarTexts } from "../schema-types"
import { initMainTouchBar } from "./touchbar" import { initMainTouchBar } from "./touchbar"
import fontList = require("font-list")
export function setUtilsListeners(manager: WindowManager) { export function setUtilsListeners(manager: WindowManager) {
async function openExternal(url: string, background = false) { async function openExternal(url: string, background = false) {
@ -28,11 +21,15 @@ export function setUtilsListeners(manager: WindowManager) {
} }
app.on("web-contents-created", (_, contents) => { app.on("web-contents-created", (_, contents) => {
// TODO: Use contents.setWindowOpenHandler instead of new-window listener contents.setWindowOpenHandler(details => {
contents.on("new-window", (event, url, _, disposition) => {
if (manager.hasWindow()) event.preventDefault()
if (contents.getType() === "webview") if (contents.getType() === "webview")
openExternal(url, disposition === "background-tab") openExternal(
details.url,
details.disposition === "background-tab"
)
return {
action: manager.hasWindow() ? "deny" : "allow",
}
}) })
contents.on("will-navigate", (event, url) => { contents.on("will-navigate", (event, url) => {
event.preventDefault() event.preventDefault()
@ -283,4 +280,10 @@ export function setUtilsListeners(manager: WindowManager) {
ipcMain.handle("touchbar-destroy", () => { ipcMain.handle("touchbar-destroy", () => {
if (manager.hasWindow()) manager.mainWindow.setTouchBar(null) if (manager.hasWindow()) manager.mainWindow.setTouchBar(null)
}) })
ipcMain.handle("init-font-list", () => {
return fontList.getFonts({
disableQuoting: true,
})
})
} }

View File

@ -63,14 +63,13 @@ export class WindowManager {
show: false, show: false,
webPreferences: { webPreferences: {
webviewTag: true, webviewTag: true,
enableRemoteModule: false,
contextIsolation: true, contextIsolation: true,
worldSafeExecuteJavaScript: true,
spellcheck: false, spellcheck: false,
preload: path.join( preload: path.join(
app.getAppPath(), app.getAppPath(),
(app.isPackaged ? "dist/" : "") + "preload.js" (app.isPackaged ? "dist/" : "") + "preload.js"
), ),
nativeWindowOpen: false,
}, },
}) })
this.mainWindowState.manage(this.mainWindow) this.mainWindowState.manage(this.mainWindow)

View File

@ -88,6 +88,7 @@ export type SchemaTypes = {
locale: string locale: string
sourceGroups: SourceGroup[] sourceGroups: SourceGroup[]
fontSize: number fontSize: number
fontFamily: string
menuOn: boolean menuOn: boolean
fetchInterval: number fetchInterval: number
searchEngine: SearchEngines searchEngine: SearchEngines

View File

@ -4,7 +4,7 @@ import lf from "lovefield"
import { RSSSource } from "./models/source" import { RSSSource } from "./models/source"
import { RSSItem } from "./models/item" import { RSSItem } from "./models/item"
const sdbSchema = lf.schema.create("sourcesDB", 1) const sdbSchema = lf.schema.create("sourcesDB", 2)
sdbSchema sdbSchema
.createTable("sources") .createTable("sources")
.addColumn("sid", lf.Type.INTEGER) .addColumn("sid", lf.Type.INTEGER)
@ -17,6 +17,7 @@ sdbSchema
.addColumn("serviceRef", lf.Type.STRING) .addColumn("serviceRef", lf.Type.STRING)
.addColumn("fetchFrequency", lf.Type.NUMBER) .addColumn("fetchFrequency", lf.Type.NUMBER)
.addColumn("rules", lf.Type.OBJECT) .addColumn("rules", lf.Type.OBJECT)
.addColumn("textDir", lf.Type.NUMBER)
.addNullable(["iconurl", "serviceRef", "rules"]) .addNullable(["iconurl", "serviceRef", "rules"])
.addIndex("idxURL", ["url"], true) .addIndex("idxURL", ["url"], true)
@ -48,8 +49,15 @@ export let sources: lf.schema.Table
export let itemsDB: lf.Database export let itemsDB: lf.Database
export let items: lf.schema.Table export let items: lf.schema.Table
async function onUpgradeSourceDB(rawDb: lf.raw.BackStore) {
const version = rawDb.getVersion()
if (version < 2) {
await rawDb.addTableColumn("sources", "textDir", 0)
}
}
export async function init() { export async function init() {
sourcesDB = await sdbSchema.connect() sourcesDB = await sdbSchema.connect({ onUpgrade: onUpgradeSourceDB })
sources = sourcesDB.getSchema().table("sources") sources = sourcesDB.getSchema().table("sources")
itemsDB = await idbSchema.connect() itemsDB = await idbSchema.connect()
items = itemsDB.getSchema().table("items") items = itemsDB.getSchema().table("items")
@ -90,6 +98,7 @@ async function migrateNeDB() {
// @ts-ignore // @ts-ignore
delete doc._id delete doc._id
if (!doc.fetchFrequency) doc.fetchFrequency = 0 if (!doc.fetchFrequency) doc.fetchFrequency = 0
doc.textDir = 0
return sources.createRow(doc) return sources.createRow(doc)
}) })
const iRows = itemDocs.map(doc => { const iRows = itemDocs.map(doc => {

View File

@ -18,5 +18,6 @@ Currently, Fluent Reader supports the following languages.
| nl | Nederlands | [@Vistaus](https://github.com/Vistaus) | | nl | Nederlands | [@Vistaus](https://github.com/Vistaus) |
| it | Italiano | [@andrewasd](https://github.com/andrewasd) | | it | Italiano | [@andrewasd](https://github.com/andrewasd) |
| pt-BR | Português do Brasil | [@fabianski7](https://github.com/fabianski7) | | pt-BR | Português do Brasil | [@fabianski7](https://github.com/fabianski7) |
| ko | 한글 | [@1drive](https://github.com/1drive) |
Refer to the repo of [react-intl-universal](https://github.com/alibaba/react-intl-universal) to get started on internationalization. Refer to the repo of [react-intl-universal](https://github.com/alibaba/react-intl-universal) to get started on internationalization.

View File

@ -12,6 +12,7 @@ import it from "./it.json"
import uk from "./uk.json" import uk from "./uk.json"
import pt_BR from "./pt-BR.json" import pt_BR from "./pt-BR.json"
import fi_FI from "./fi-FI.json" import fi_FI from "./fi-FI.json"
import ko from "./ko.json"
const locales = { const locales = {
"en-US": en_US, "en-US": en_US,
@ -28,6 +29,7 @@ const locales = {
"uk": uk, "uk": uk,
"pt-BR": pt_BR, "pt-BR": pt_BR,
"fi-FI": fi_FI, "fi-FI": fi_FI,
"ko": ko,
} }
export default locales export default locales

View File

@ -18,6 +18,7 @@
"confirmMarkAll": "Do you really want to mark all articles on this page as read?", "confirmMarkAll": "Do you really want to mark all articles on this page as read?",
"confirm": "Confirm", "confirm": "Confirm",
"cancel": "Cancel", "cancel": "Cancel",
"default": "Default",
"time": { "time": {
"now": "now", "now": "now",
"m": "m", "m": "m",
@ -66,7 +67,12 @@
"loadWebpage": "Load webpage", "loadWebpage": "Load webpage",
"loadFull": "Load full content", "loadFull": "Load full content",
"notify": "Notify if fetched in background", "notify": "Notify if fetched in background",
"dontNotify": "Don't notify" "dontNotify": "Don't notify",
"textDir": "Text direction",
"LTR": "Left-to-right",
"RTL": "Right-to-left",
"Vertical": "Vertical",
"font": "Font"
}, },
"context": { "context": {
"share": "Share", "share": "Share",

View File

@ -211,7 +211,7 @@
"cacheSize": "Cache-lagra {size} data", "cacheSize": "Cache-lagra {size} data",
"deleteChoices": "Ta bort artiklar från ... dagar sedan", "deleteChoices": "Ta bort artiklar från ... dagar sedan",
"confirmDelete": "Ta bort", "confirmDelete": "Ta bort",
"daysAgo": "{day, plural, =1 {# dag} other {# dagar}} sedan", "daysAgo": "{days, plural, =1 {# dag} other {# dagar}} sedan",
"deleteAll": "Ta bort alla artiklar", "deleteAll": "Ta bort alla artiklar",
"calculatingSize": "Beräknar storlek...", "calculatingSize": "Beräknar storlek...",
"itemSize": "Omkring {size} lokal datalagring upptas av artiklar", "itemSize": "Omkring {size} lokal datalagring upptas av artiklar",

View File

@ -18,6 +18,7 @@
"confirmMarkAll": "确认将本页所有文章标为已读?", "confirmMarkAll": "确认将本页所有文章标为已读?",
"confirm": "确认", "confirm": "确认",
"cancel": "取消", "cancel": "取消",
"default": "默认",
"time": { "time": {
"now": "now", "now": "now",
"m": "m", "m": "m",
@ -66,7 +67,12 @@
"loadWebpage": "加载网页", "loadWebpage": "加载网页",
"loadFull": "抓取全文", "loadFull": "抓取全文",
"notify": "后台抓取时发送通知", "notify": "后台抓取时发送通知",
"dontNotify": "不发送通知" "dontNotify": "不发送通知",
"textDir": "文本方向",
"LTR": "从左到右",
"RTL": "从右到左",
"Vertical": "纵书",
"font": "字体"
}, },
"context": { "context": {
"share": "分享", "share": "分享",

View File

@ -18,6 +18,7 @@
"confirmMarkAll": "確認將本頁所有文章標為已讀?", "confirmMarkAll": "確認將本頁所有文章標為已讀?",
"confirm": "確認", "confirm": "確認",
"cancel": "取消", "cancel": "取消",
"default": "預設",
"time": { "time": {
"now": "now", "now": "now",
"m": "m", "m": "m",
@ -66,7 +67,12 @@
"loadWebpage": "載入網頁", "loadWebpage": "載入網頁",
"loadFull": "抓取全文", "loadFull": "抓取全文",
"notify": "後臺抓取時傳送通知", "notify": "後臺抓取時傳送通知",
"dontNotify": "不傳送通知" "dontNotify": "不傳送通知",
"textDir": "文本方向",
"LTR": "從左到右",
"RTL": "從右到左",
"Vertical": "縱書",
"font": "字體"
}, },
"context": { "context": {
"share": "分享", "share": "分享",

View File

@ -312,7 +312,8 @@ const markUnreadDone = (item: RSSItem): ItemActionTypes => ({
}) })
export function markRead(item: RSSItem): AppThunk { export function markRead(item: RSSItem): AppThunk {
return dispatch => { return (dispatch, getState) => {
item = getState().items[item._id]
if (!item.hasRead) { if (!item.hasRead) {
db.itemsDB db.itemsDB
.update(db.items) .update(db.items)
@ -377,7 +378,8 @@ export function markAllRead(
} }
export function markUnread(item: RSSItem): AppThunk { export function markUnread(item: RSSItem): AppThunk {
return dispatch => { return (dispatch, getState) => {
item = getState().items[item._id]
if (item.hasRead) { if (item.hasRead) {
db.itemsDB db.itemsDB
.update(db.items) .update(db.items)

View File

@ -23,6 +23,12 @@ export const enum SourceOpenTarget {
FullContent, FullContent,
} }
export const enum SourceTextDirection {
LTR,
RTL,
Vertical,
}
export class RSSSource { export class RSSSource {
sid: number sid: number
url: string url: string
@ -34,6 +40,7 @@ export class RSSSource {
serviceRef?: string serviceRef?: string
fetchFrequency: number // in minutes fetchFrequency: number // in minutes
rules?: SourceRule[] rules?: SourceRule[]
textDir: SourceTextDirection
constructor(url: string, name: string = null) { constructor(url: string, name: string = null) {
this.url = url this.url = url
@ -41,6 +48,7 @@ export class RSSSource {
this.openTarget = SourceOpenTarget.Local this.openTarget = SourceOpenTarget.Local
this.lastFetched = new Date() this.lastFetched = new Date()
this.fetchFrequency = 0 this.fetchFrequency = 0
this.textDir = SourceTextDirection.LTR
} }
static async fetchMetaData(source: RSSSource) { static async fetchMetaData(source: RSSSource) {
@ -483,7 +491,7 @@ export function sourceReducer(
} }
case MARK_ALL_READ: { case MARK_ALL_READ: {
let nextState = { ...state } let nextState = { ...state }
action.sids.map((sid, i) => { action.sids.forEach(sid => {
nextState[sid] = { nextState[sid] = {
...state[sid], ...state[sid],
unreadCount: action.time ? state[sid].unreadCount : 0, unreadCount: action.time ? state[sid].unreadCount : 0,

View File

@ -3,6 +3,7 @@ import { IPartialTheme, loadTheme } from "@fluentui/react"
import locales from "./i18n/_locales" import locales from "./i18n/_locales"
import { ThemeSettings } from "../schema-types" import { ThemeSettings } from "../schema-types"
import intl from "react-intl-universal" import intl from "react-intl-universal"
import { SourceTextDirection } from "./models/source"
const lightTheme: IPartialTheme = { const lightTheme: IPartialTheme = {
defaultFontStyle: { defaultFontStyle: {
@ -119,6 +120,7 @@ export async function importAll() {
} else { } else {
const sRows = configs.lovefield.sources.map(s => { const sRows = configs.lovefield.sources.map(s => {
s.lastFetched = new Date(s.lastFetched) s.lastFetched = new Date(s.lastFetched)
if (!s.textDir) s.textDir = SourceTextDirection.LTR
return db.sources.createRow(s) return db.sources.createRow(s)
}) })
const iRows = configs.lovefield.items.map(i => { const iRows = configs.lovefield.items.map(i => {

View File

@ -23,6 +23,9 @@ module.exports = [
path: __dirname + "/dist", path: __dirname + "/dist",
filename: "electron.js", filename: "electron.js",
}, },
node: {
__dirname: false,
},
plugins: [new HardSourceWebpackPlugin()], plugins: [new HardSourceWebpackPlugin()],
}, },
{ {