commit
06abd8bc21
|
@ -29,7 +29,7 @@ If you are using macOS or an older version of Windows, you can also [get Fluent
|
||||||
|
|
||||||
### Contribute
|
### Contribute
|
||||||
|
|
||||||
Make Fluent Reader better by reporting bugs or opening feature requests through [GitHub issues](https://github.com/yang991178/fluent-reader/issues).
|
Help make Fluent Reader better by reporting bugs or opening feature requests through [GitHub issues](https://github.com/yang991178/fluent-reader/issues).
|
||||||
|
|
||||||
You can also help internationalize the app by providing [translations into additional languages](https://github.com/yang991178/fluent-reader/tree/master/src/scripts/i18n).
|
You can also help internationalize the app by providing [translations into additional languages](https://github.com/yang991178/fluent-reader/tree/master/src/scripts/i18n).
|
||||||
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.
|
||||||
|
|
|
@ -45,9 +45,6 @@ html, body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
background-color: var(--neutralLighterAlt);
|
|
||||||
}
|
|
||||||
#root {
|
#root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
@ -157,6 +154,9 @@ nav .progress {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
body.darwin .btn-group .seperator {
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
.btn-group .seperator::before {
|
.btn-group .seperator::before {
|
||||||
content: "|";
|
content: "|";
|
||||||
}
|
}
|
||||||
|
@ -184,12 +184,15 @@ nav.menu-on .btn-group .btn.system, nav.hide-btns .btn-group .btn.system {
|
||||||
nav.menu-on .btn-group .btn.system, nav.item-on .btn-group .btn.system {
|
nav.menu-on .btn-group .btn.system, nav.item-on .btn-group .btn.system {
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
.btn-group .btn:hover {
|
.btn-group .btn:hover, .ms-Nav-compositeLink:hover {
|
||||||
background-color: #0001;
|
background-color: #0001;
|
||||||
}
|
}
|
||||||
.btn-group .btn:active {
|
.btn-group .btn:active, .ms-Nav-compositeLink:active {
|
||||||
background-color: #0002;
|
background-color: #0002;
|
||||||
}
|
}
|
||||||
|
.ms-Nav-compositeLink:hover .ms-Nav-link {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
.btn-group .btn.disabled, .btn-group .btn.fetching {
|
.btn-group .btn.disabled, .btn-group .btn.fetching {
|
||||||
background-color: unset !important;
|
background-color: unset !important;
|
||||||
color: var(--neutralSecondaryAlt);
|
color: var(--neutralSecondaryAlt);
|
||||||
|
@ -263,7 +266,7 @@ nav.menu-on .btn-group .btn.system, nav.item-on .btn-group .btn.system {
|
||||||
|
|
||||||
.settings-container {
|
.settings-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 5;
|
z-index: 7;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -295,7 +298,7 @@ div[role="tabpanel"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #fffa;
|
background-color: #fffa;
|
||||||
z-index: 6;
|
z-index: 8;
|
||||||
}
|
}
|
||||||
.settings .loading .ms-Spinner {
|
.settings .loading .ms-Spinner {
|
||||||
margin-top: 180px;
|
margin-top: 180px;
|
||||||
|
@ -346,16 +349,18 @@ img.favicon {
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
height: calc(100% - 32px);
|
margin-top: -32px;
|
||||||
overflow-y: scroll;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--neutralLighterAlt);
|
||||||
}
|
}
|
||||||
.main::before {
|
.main::before {
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 32px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: calc(100% - 16px);
|
||||||
height: 32px;
|
height: 32px;
|
||||||
margin-bottom: -32px;
|
margin-bottom: -32px;
|
||||||
background: linear-gradient(var(--neutralLighterAlt), #faf9f800);
|
background: linear-gradient(var(--neutralLighterAlt), #faf9f800);
|
||||||
|
@ -390,10 +395,15 @@ img.favicon {
|
||||||
}
|
}
|
||||||
.menu-container {
|
.menu-container {
|
||||||
width: 280px;
|
width: 280px;
|
||||||
|
background: none;
|
||||||
|
backdrop-filter: none;
|
||||||
}
|
}
|
||||||
.menu-container .menu {
|
.menu-container .menu {
|
||||||
background-color: var(--neutralLight);
|
background-color: var(--neutralLight);
|
||||||
}
|
}
|
||||||
|
body.darwin .menu-container .menu {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
.menu-container .menu::after {
|
.menu-container .menu::after {
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -564,6 +574,9 @@ img.favicon {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
overflow: hidden scroll;
|
||||||
|
margin-top: 32px;
|
||||||
}
|
}
|
||||||
.cards-feed-container > div.load-more-wrapper, .flex-fix {
|
.cards-feed-container > div.load-more-wrapper, .flex-fix {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -654,11 +667,13 @@ img.favicon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
animation-fill-mode: none;
|
animation-fill-mode: none;
|
||||||
}
|
}
|
||||||
.card:hover, .card:focus {
|
.card:focus, .list-card:focus {
|
||||||
box-shadow: #0006 0px 5px 40px;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
.card:focus::after, .list-card:focus::after {
|
.card:hover, .ms-Fabric--isFocusVisible .card:focus {
|
||||||
|
box-shadow: #0006 0px 5px 40px;
|
||||||
|
}
|
||||||
|
.ms-Fabric--isFocusVisible .card:focus::after, .ms-Fabric--isFocusVisible .list-card:focus::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
|
@ -698,7 +713,9 @@ img.favicon {
|
||||||
transition: transform ease-out .12s;
|
transition: transform ease-out .12s;
|
||||||
}
|
}
|
||||||
.card.transform:hover img.head, .card.transform:hover p, .card.transform:hover h3,
|
.card.transform:hover img.head, .card.transform:hover p, .card.transform:hover h3,
|
||||||
.card.transform:focus img.head, .card.transform:focus p, .card.transform:focus h3 {
|
.ms-Fabric--isFocusVisible .card.transform:focus img.head,
|
||||||
|
.ms-Fabric--isFocusVisible .card.transform:focus p,
|
||||||
|
.ms-Fabric--isFocusVisible .card.transform:focus h3 {
|
||||||
transform: translateY(-144px);
|
transform: translateY(-144px);
|
||||||
}
|
}
|
||||||
.card h3.title {
|
.card h3.title {
|
||||||
|
@ -752,9 +769,8 @@ img.favicon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: #0000 0px 5px 15px;
|
box-shadow: #0000 0px 5px 15px;
|
||||||
}
|
}
|
||||||
.list-card:hover, .list-card:focus {
|
.list-card:hover, .ms-Fabric--isFocusVisible .list-card:focus {
|
||||||
box-shadow: #0004 0px 5px 15px;
|
box-shadow: #0004 0px 5px 15px;
|
||||||
outline: none;
|
|
||||||
}
|
}
|
||||||
.list-card:active {
|
.list-card:active {
|
||||||
box-shadow: #0000 0px 5px 15px, inset #0004 0px 0px 15px;
|
box-shadow: #0000 0px 5px 15px, inset #0004 0px 0px 15px;
|
||||||
|
@ -793,10 +809,10 @@ img.favicon {
|
||||||
.ms-Button--commandBar.active .ms-Button-icon {
|
.ms-Button--commandBar.active .ms-Button-icon {
|
||||||
color: #c7e0f4;
|
color: #c7e0f4;
|
||||||
}
|
}
|
||||||
.btn-group .btn:hover {
|
.btn-group .btn:hover, .ms-Nav-compositeLink:hover {
|
||||||
background-color: #fff1;
|
background-color: #fff1;
|
||||||
}
|
}
|
||||||
.btn-group .btn:active {
|
.btn-group .btn:active, .ms-Nav-compositeLink:active {
|
||||||
background-color: #fff2;
|
background-color: #fff2;
|
||||||
}
|
}
|
||||||
.settings .loading {
|
.settings .loading {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "fluent-reader",
|
"name": "fluent-reader",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"description": "A simplistic, modern desktop RSS reader",
|
"description": "A simplistic, modern desktop RSS reader",
|
||||||
"main": "./dist/electron.js",
|
"main": "./dist/electron.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -31,7 +31,6 @@ type ArticleState = {
|
||||||
|
|
||||||
class Article extends React.Component<ArticleProps, ArticleState> {
|
class Article extends React.Component<ArticleProps, ArticleState> {
|
||||||
webview: Electron.WebviewTag
|
webview: Electron.WebviewTag
|
||||||
shouldRefocus = false
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
@ -107,18 +106,28 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
||||||
if (input.type === "keyDown") {
|
if (input.type === "keyDown") {
|
||||||
switch (input.key) {
|
switch (input.key) {
|
||||||
case "Escape":
|
case "Escape":
|
||||||
this.shouldRefocus = true
|
|
||||||
this.props.dismiss()
|
this.props.dismiss()
|
||||||
break
|
break
|
||||||
case "ArrowLeft":
|
case "ArrowLeft":
|
||||||
case "ArrowRight":
|
case "ArrowRight":
|
||||||
this.props.offsetItem(input.key === "ArrowLeft" ? -1 : 1)
|
this.props.offsetItem(input.key === "ArrowLeft" ? -1 : 1)
|
||||||
break
|
break
|
||||||
case "l":
|
case "l": case "L":
|
||||||
this.toggleWebpage()
|
this.toggleWebpage()
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
this.props.shortcuts(this.props.item, input.key)
|
this.props.shortcuts(this.props.item, input.key)
|
||||||
|
const keyboardEvent = new KeyboardEvent("keydown", {
|
||||||
|
code: input.code,
|
||||||
|
key: input.key,
|
||||||
|
shiftKey: input.shift,
|
||||||
|
altKey: input.alt,
|
||||||
|
ctrlKey: input.control,
|
||||||
|
metaKey: input.meta,
|
||||||
|
repeat: input.isAutoRepeat,
|
||||||
|
bubbles: true
|
||||||
|
})
|
||||||
|
document.dispatchEvent(keyboardEvent)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,10 +158,8 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount = () => {
|
componentWillUnmount = () => {
|
||||||
if (this.shouldRefocus) {
|
let refocus = document.querySelector(`#refocus>div[data-iid="${this.props.item._id}"]`) as HTMLElement
|
||||||
let refocus = document.querySelector(`#refocus>div[data-iid="${this.props.item._id}"]`) as HTMLElement
|
if (refocus) refocus.focus()
|
||||||
if (refocus) refocus.focus()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openInBrowser = () => {
|
openInBrowser = () => {
|
||||||
|
|
|
@ -17,7 +17,6 @@ class DefaultCard extends Card {
|
||||||
className={this.className()}
|
className={this.className()}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
onMouseUp={this.onMouseUp}
|
onMouseUp={this.onMouseUp}
|
||||||
onMouseDown={event => event.preventDefault()}
|
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
data-iid={this.props.item._id}
|
data-iid={this.props.item._id}
|
||||||
data-is-focusable>
|
data-is-focusable>
|
||||||
|
|
|
@ -16,7 +16,6 @@ class ListCard extends Card {
|
||||||
className={this.className()}
|
className={this.className()}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
onMouseUp={this.onMouseUp}
|
onMouseUp={this.onMouseUp}
|
||||||
onMouseDown={event => event.preventDefault()}
|
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
data-iid={this.props.item._id}
|
data-iid={this.props.item._id}
|
||||||
data-is-focusable>
|
data-is-focusable>
|
||||||
|
|
|
@ -91,8 +91,10 @@ export class Menu extends React.Component<MenuProps> {
|
||||||
let [type, index] = item.key.split("-")
|
let [type, index] = item.key.split("-")
|
||||||
if (type === "s") {
|
if (type === "s") {
|
||||||
sids = [parseInt(index)]
|
sids = [parseInt(index)]
|
||||||
} else {
|
} else if (type === "g") {
|
||||||
sids = this.props.groups[parseInt(index)].sids
|
sids = this.props.groups[parseInt(index)].sids
|
||||||
|
} else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
this.props.groupContextMenu(sids, event)
|
this.props.groupContextMenu(sids, event)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,14 +7,15 @@ import { ProgressIndicator } from "@fluentui/react"
|
||||||
import { getWindowBreakpoint } from "../scripts/utils"
|
import { getWindowBreakpoint } from "../scripts/utils"
|
||||||
|
|
||||||
type NavProps = {
|
type NavProps = {
|
||||||
state: AppState,
|
state: AppState
|
||||||
itemShown: boolean,
|
itemShown: boolean
|
||||||
fetch: () => void,
|
menu: () => void
|
||||||
menu: () => void,
|
search: () => void
|
||||||
logs: () => void,
|
|
||||||
views: () => void,
|
|
||||||
settings: () => void,
|
|
||||||
markAllRead: () => void
|
markAllRead: () => void
|
||||||
|
fetch: () => void
|
||||||
|
logs: () => void
|
||||||
|
views: () => void
|
||||||
|
settings: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type NavState = {
|
type NavState = {
|
||||||
|
@ -44,6 +45,9 @@ class Nav extends React.Component<NavProps, NavState> {
|
||||||
case "F1":
|
case "F1":
|
||||||
this.props.menu()
|
this.props.menu()
|
||||||
break
|
break
|
||||||
|
case "F2":
|
||||||
|
this.props.search()
|
||||||
|
break
|
||||||
case "F5":
|
case "F5":
|
||||||
this.fetch()
|
this.fetch()
|
||||||
break
|
break
|
||||||
|
|
|
@ -7,6 +7,7 @@ import ArticleSearch from "./utils/article-search"
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
menuOn: boolean
|
menuOn: boolean
|
||||||
|
contextOn: boolean
|
||||||
settingsOn: boolean
|
settingsOn: boolean
|
||||||
feeds: string[]
|
feeds: string[]
|
||||||
itemId: string
|
itemId: string
|
||||||
|
@ -35,6 +36,7 @@ class Page extends React.Component<PageProps> {
|
||||||
</div>}
|
</div>}
|
||||||
{this.props.itemId && (
|
{this.props.itemId && (
|
||||||
<FocusTrapZone
|
<FocusTrapZone
|
||||||
|
disabled={this.props.contextOn}
|
||||||
ignoreExternalFocusing={true}
|
ignoreExternalFocusing={true}
|
||||||
isClickableOutsideFocusTrap={true}
|
isClickableOutsideFocusTrap={true}
|
||||||
className="article-container"
|
className="article-container"
|
||||||
|
|
|
@ -214,12 +214,13 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||||
|
|
||||||
handleInputChange = (event) => {
|
handleInputChange = (event) => {
|
||||||
const name: string = event.target.name
|
const name: string = event.target.name
|
||||||
this.setState({[name]: event.target.value.trim()})
|
this.setState({[name]: event.target.value})
|
||||||
}
|
}
|
||||||
|
|
||||||
createGroup = (event: React.FormEvent) => {
|
createGroup = (event: React.FormEvent) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (this.state.newGroupName.length > 0) this.props.createGroup(this.state.newGroupName)
|
let trimmed = this.state.newGroupName.trim()
|
||||||
|
if (trimmed.length > 0) this.props.createGroup(trimmed)
|
||||||
}
|
}
|
||||||
|
|
||||||
addToGroup = () => {
|
addToGroup = () => {
|
||||||
|
@ -239,7 +240,7 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||||
|
|
||||||
updateGroupName = () => {
|
updateGroupName = () => {
|
||||||
let group = this.state.selectedGroup
|
let group = this.state.selectedGroup
|
||||||
group = { ...group, name: this.state.editGroupName }
|
group = { ...group, name: this.state.editGroupName.trim() }
|
||||||
this.props.updateGroup(group)
|
this.props.updateGroup(group)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,7 +293,7 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
disabled={this.state.newGroupName.length == 0}
|
disabled={this.state.newGroupName.trim().length == 0}
|
||||||
type="sumbit"
|
type="sumbit"
|
||||||
text={intl.get("create")} />
|
text={intl.get("create")} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
@ -325,7 +326,7 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
disabled={this.state.editGroupName.length == 0}
|
disabled={this.state.editGroupName.trim().length == 0}
|
||||||
onClick={this.updateGroupName}
|
onClick={this.updateGroupName}
|
||||||
text={intl.get("groups.editName")} />
|
text={intl.get("groups.editName")} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
|
|
@ -101,12 +101,13 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
||||||
|
|
||||||
handleInputChange = (event) => {
|
handleInputChange = (event) => {
|
||||||
const name: string = event.target.name
|
const name: string = event.target.name
|
||||||
this.setState({[name]: event.target.value.trim()})
|
this.setState({[name]: event.target.value})
|
||||||
}
|
}
|
||||||
|
|
||||||
addSource = (event: React.FormEvent) => {
|
addSource = (event: React.FormEvent) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (urlTest(this.state.newUrl)) this.props.addSource(this.state.newUrl)
|
let trimmed = this.state.newUrl.trim()
|
||||||
|
if (urlTest(trimmed)) this.props.addSource(trimmed)
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenTargetChange = (_, option: IChoiceGroupOption) => {
|
onOpenTargetChange = (_, option: IChoiceGroupOption) => {
|
||||||
|
@ -142,7 +143,7 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
disabled={!urlTest(this.state.newUrl)}
|
disabled={!urlTest(this.state.newUrl.trim())}
|
||||||
type="submit"
|
type="submit"
|
||||||
text={intl.get("add")} />
|
text={intl.get("add")} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
@ -171,8 +172,8 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
disabled={this.state.newSourceName.length == 0}
|
disabled={this.state.newSourceName.trim().length == 0}
|
||||||
onClick={() => this.props.updateSourceName(this.state.selectedSource, this.state.newSourceName)}
|
onClick={() => this.props.updateSourceName(this.state.selectedSource, this.state.newSourceName.trim())}
|
||||||
text={intl.get("sources.editName")} />
|
text={intl.get("sources.editName")} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
@ -41,6 +41,7 @@ class ArticleSearch extends React.Component<SearchProps, SearchState> {
|
||||||
|
|
||||||
componentDidUpdate(prevProps: SearchProps) {
|
componentDidUpdate(prevProps: SearchProps) {
|
||||||
if (this.props.searchOn && !prevProps.searchOn) {
|
if (this.props.searchOn && !prevProps.searchOn) {
|
||||||
|
this.setState({ query: this.props.initQuery })
|
||||||
this.inputRef.current.focus()
|
this.inputRef.current.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { createSelector } from "reselect"
|
||||||
import { RootState } from "../scripts/reducer"
|
import { RootState } from "../scripts/reducer"
|
||||||
import { fetchItems, markAllRead } from "../scripts/models/item"
|
import { fetchItems, markAllRead } from "../scripts/models/item"
|
||||||
import { toggleMenu, toggleLogMenu, toggleSettings, openViewMenu } from "../scripts/models/app"
|
import { toggleMenu, toggleLogMenu, toggleSettings, openViewMenu } from "../scripts/models/app"
|
||||||
import { ViewType } from "../scripts/models/page"
|
import { ViewType, toggleSearch } from "../scripts/models/page"
|
||||||
import Nav from "../components/nav"
|
import Nav from "../components/nav"
|
||||||
|
|
||||||
const getState = (state: RootState) => state.app
|
const getState = (state: RootState) => state.app
|
||||||
|
@ -25,6 +25,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
logs: () => dispatch(toggleLogMenu()),
|
logs: () => dispatch(toggleLogMenu()),
|
||||||
views: () => dispatch(openViewMenu()),
|
views: () => dispatch(openViewMenu()),
|
||||||
settings: () => dispatch(toggleSettings()),
|
settings: () => dispatch(toggleSettings()),
|
||||||
|
search: () => dispatch(toggleSearch()),
|
||||||
markAllRead: () => {
|
markAllRead: () => {
|
||||||
remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
title: intl.get("nav.markAllRead"),
|
title: intl.get("nav.markAllRead"),
|
||||||
|
|
|
@ -4,17 +4,20 @@ import { RootState } from "../scripts/reducer"
|
||||||
import Page from "../components/page"
|
import Page from "../components/page"
|
||||||
import { AppDispatch } from "../scripts/utils"
|
import { AppDispatch } from "../scripts/utils"
|
||||||
import { dismissItem, showOffsetItem } from "../scripts/models/page"
|
import { dismissItem, showOffsetItem } from "../scripts/models/page"
|
||||||
|
import { ContextMenuType } from "../scripts/models/app"
|
||||||
|
|
||||||
const getPage = (state: RootState) => state.page
|
const getPage = (state: RootState) => state.page
|
||||||
const getSettings = (state: RootState) => state.app.settings.display
|
const getSettings = (state: RootState) => state.app.settings.display
|
||||||
const getMenu = (state: RootState) => state.app.menu
|
const getMenu = (state: RootState) => state.app.menu
|
||||||
|
const getContext = (state: RootState) => state.app.contextMenu.type != ContextMenuType.Hidden
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
[getPage, getSettings, getMenu],
|
[getPage, getSettings, getMenu, getContext],
|
||||||
(page, settingsOn, menuOn) => ({
|
(page, settingsOn, menuOn, contextOn) => ({
|
||||||
feeds: [page.feedId],
|
feeds: [page.feedId],
|
||||||
settingsOn: settingsOn,
|
settingsOn: settingsOn,
|
||||||
menuOn: menuOn,
|
menuOn: menuOn,
|
||||||
|
contextOn: contextOn,
|
||||||
itemId: page.itemId,
|
itemId: page.itemId,
|
||||||
viewType: page.viewType
|
viewType: page.viewType
|
||||||
})
|
})
|
||||||
|
|
|
@ -29,7 +29,8 @@ function createWindow() {
|
||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
title: "Fluent Reader",
|
title: "Fluent Reader",
|
||||||
backgroundColor: nativeTheme.shouldUseDarkColors ? "#282828" : "#faf9f8",
|
backgroundColor: process.platform === "darwin" ? "#00000000" : (nativeTheme.shouldUseDarkColors ? "#282828" : "#faf9f8"),
|
||||||
|
vibrancy: "sidebar",
|
||||||
x: mainWindowState.x,
|
x: mainWindowState.x,
|
||||||
y: mainWindowState.y,
|
y: mainWindowState.y,
|
||||||
width: mainWindowState.width,
|
width: mainWindowState.width,
|
||||||
|
@ -55,7 +56,7 @@ function createWindow() {
|
||||||
mainWindow.loadFile((app.isPackaged ? "dist/" : "") + "index.html")
|
mainWindow.loadFile((app.isPackaged ? "dist/" : "") + "index.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === "darwin") {
|
||||||
const template = [
|
const template = [
|
||||||
{
|
{
|
||||||
label: "Application",
|
label: "Application",
|
||||||
|
@ -66,8 +67,12 @@ if (process.platform === 'darwin') {
|
||||||
{
|
{
|
||||||
label: "Edit",
|
label: "Edit",
|
||||||
submenu: [
|
submenu: [
|
||||||
|
{ label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" },
|
||||||
|
{ label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" },
|
||||||
|
{ label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" },
|
||||||
{ label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" },
|
{ label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" },
|
||||||
{ label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" },
|
{ label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" },
|
||||||
|
{ label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -199,6 +199,7 @@ export function initIntl(): AppThunk<Promise<void>> {
|
||||||
|
|
||||||
export function initApp(): AppThunk {
|
export function initApp(): AppThunk {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
|
document.body.classList.add(process.platform)
|
||||||
dispatch(initIntl()).then(() =>
|
dispatch(initIntl()).then(() =>
|
||||||
dispatch(initSources())
|
dispatch(initSources())
|
||||||
).then(() =>
|
).then(() =>
|
||||||
|
@ -266,10 +267,7 @@ export function appReducer(
|
||||||
}
|
}
|
||||||
case INIT_FEEDS:
|
case INIT_FEEDS:
|
||||||
switch (action.status) {
|
switch (action.status) {
|
||||||
case ActionStatus.Request: return {
|
case ActionStatus.Request: return state
|
||||||
...state,
|
|
||||||
feedInit: false
|
|
||||||
}
|
|
||||||
default: return {
|
default: return {
|
||||||
...state,
|
...state,
|
||||||
feedInit: true
|
feedInit: true
|
||||||
|
|
|
@ -266,17 +266,18 @@ export function toggleHidden(item: RSSItem): AppThunk {
|
||||||
export function itemShortcuts(item: RSSItem, key: string): AppThunk {
|
export function itemShortcuts(item: RSSItem, key: string): AppThunk {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "m":
|
case "m": case "M":
|
||||||
if (item.hasRead) dispatch(markUnread(item))
|
if (item.hasRead) dispatch(markUnread(item))
|
||||||
else dispatch(markRead(item))
|
else dispatch(markRead(item))
|
||||||
break
|
break
|
||||||
case "b":
|
case "b": case "B":
|
||||||
|
if (!item.hasRead) dispatch(markRead(item))
|
||||||
openExternal(item.link)
|
openExternal(item.link)
|
||||||
break
|
break
|
||||||
case "s":
|
case "s": case "S":
|
||||||
dispatch(toggleStarred(item))
|
dispatch(toggleStarred(item))
|
||||||
break
|
break
|
||||||
case "h":
|
case "h": case "H":
|
||||||
dispatch(toggleHidden(item))
|
dispatch(toggleHidden(item))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ALL, SOURCE, loadMore, FeedFilter, FilterType, initFeeds } from "./feed"
|
import { ALL, SOURCE, loadMore, FeedFilter, FilterType, initFeeds, FeedActionTypes, INIT_FEED } from "./feed"
|
||||||
import { getWindowBreakpoint, AppThunk } from "../utils"
|
import { getWindowBreakpoint, AppThunk, ActionStatus } from "../utils"
|
||||||
import { getDefaultView } from "../settings"
|
import { getDefaultView } from "../settings"
|
||||||
import { RSSItem, markRead } from "./item"
|
import { RSSItem, markRead } from "./item"
|
||||||
import { SourceActionTypes, DELETE_SOURCE } from "./source"
|
import { SourceActionTypes, DELETE_SOURCE } from "./source"
|
||||||
|
@ -104,7 +104,7 @@ export const toggleSearch = (): AppThunk => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
let state = getState()
|
let state = getState()
|
||||||
dispatch(({ type: TOGGLE_SEARCH }))
|
dispatch(({ type: TOGGLE_SEARCH }))
|
||||||
if (!getWindowBreakpoint()) {
|
if (!getWindowBreakpoint() && state.app.menu) {
|
||||||
dispatch(toggleMenu())
|
dispatch(toggleMenu())
|
||||||
}
|
}
|
||||||
if (state.page.searchOn) {
|
if (state.page.searchOn) {
|
||||||
|
@ -214,7 +214,7 @@ export class PageState {
|
||||||
|
|
||||||
export function pageReducer(
|
export function pageReducer(
|
||||||
state = new PageState(),
|
state = new PageState(),
|
||||||
action: PageActionTypes | SourceActionTypes
|
action: PageActionTypes | SourceActionTypes | FeedActionTypes
|
||||||
): PageState {
|
): PageState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SELECT_PAGE:
|
case SELECT_PAGE:
|
||||||
|
@ -244,6 +244,14 @@ export function pageReducer(
|
||||||
...state,
|
...state,
|
||||||
itemId: action.item._id
|
itemId: action.item._id
|
||||||
}
|
}
|
||||||
|
case INIT_FEED: switch (action.status) {
|
||||||
|
case ActionStatus.Success: return {
|
||||||
|
...state,
|
||||||
|
itemId: (action.feed._id === state.feedId && action.items.filter(i => i._id === state.itemId).length === 0)
|
||||||
|
? null : state.itemId
|
||||||
|
}
|
||||||
|
default: return state
|
||||||
|
}
|
||||||
case DELETE_SOURCE:
|
case DELETE_SOURCE:
|
||||||
case DISMISS_ITEM: return {
|
case DISMISS_ITEM: return {
|
||||||
...state,
|
...state,
|
||||||
|
|
Loading…
Reference in New Issue