mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-02-14 10:40:44 +01:00
text context menu & load webpage
This commit is contained in:
parent
b35f21a3cf
commit
3fb9252b58
8
dist/article/article.css
vendored
8
dist/article/article.css
vendored
@ -21,12 +21,17 @@ a:hover, a:active {
|
|||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
line-height: 1.75rem;
|
line-height: 1.75rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
|
#main > p.date {
|
||||||
|
color: #484644;
|
||||||
|
font-size: .875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
article {
|
article {
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
article > * {
|
article * {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
article img {
|
article img {
|
||||||
@ -39,4 +44,5 @@ article figure {
|
|||||||
article figure figcaption {
|
article figure figcaption {
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
color: #484644;
|
color: #484644;
|
||||||
|
-webkit-user-modify: read-only;
|
||||||
}
|
}
|
4
dist/article/article.js
vendored
4
dist/article/article.js
vendored
@ -9,3 +9,7 @@ document.addEventListener("click", event => {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (event.target.href) post("request-navigation", event.target.href)
|
if (event.target.href) post("request-navigation", event.target.href)
|
||||||
})
|
})
|
||||||
|
document.addEventListener("contextmenu", event => {
|
||||||
|
let text = document.getSelection().toString()
|
||||||
|
if (text) post("context-menu", [event.clientX, event.clientY], text)
|
||||||
|
})
|
12
dist/styles.css
vendored
12
dist/styles.css
vendored
@ -32,7 +32,13 @@ html, body {
|
|||||||
background: #a4262c;
|
background: #a4262c;
|
||||||
border-color: #a4262c;
|
border-color: #a4262c;
|
||||||
}
|
}
|
||||||
|
.ms-Button--commandBar.active {
|
||||||
|
background-color: rgb(237, 235, 233);
|
||||||
|
color: rgb(32, 31, 30);
|
||||||
|
}
|
||||||
|
.ms-Button--commandBar.active .ms-Button-icon {
|
||||||
|
color: rgb(0, 90, 158);
|
||||||
|
}
|
||||||
i.ms-Nav-chevron {
|
i.ms-Nav-chevron {
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@ -43,7 +49,7 @@ i.ms-Nav-chevron {
|
|||||||
.ms-ActivityItem-activityTypeIcon, .ms-ActivityItem-timeStamp {
|
.ms-ActivityItem-activityTypeIcon, .ms-ActivityItem-timeStamp {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
.ms-Label {
|
.ms-Label, .ms-Spinner-label {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,6 +238,7 @@ img.favicon {
|
|||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
.ms-DetailsList-contentWrapper {
|
.ms-DetailsList-contentWrapper {
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
@ -329,6 +336,7 @@ img.favicon {
|
|||||||
}
|
}
|
||||||
.article .actions .source-name {
|
.article .actions .source-name {
|
||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cards-feed-container {
|
.cards-feed-container {
|
||||||
|
@ -13,10 +13,12 @@ type ArticleProps = {
|
|||||||
source: RSSSource
|
source: RSSSource
|
||||||
dismiss: () => void
|
dismiss: () => void
|
||||||
toggleHasRead: (item: RSSItem) => void
|
toggleHasRead: (item: RSSItem) => void
|
||||||
|
textMenu: (text: string, position: [number, number]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArticleState = {
|
type ArticleState = {
|
||||||
fontSize: number
|
fontSize: number
|
||||||
|
loadWebpage: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
class Article extends React.Component<ArticleProps, ArticleState> {
|
class Article extends React.Component<ArticleProps, ArticleState> {
|
||||||
@ -25,7 +27,8 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
fontSize: this.getFontSize()
|
fontSize: this.getFontSize(),
|
||||||
|
loadWebpage: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,33 +52,55 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ipcHandler = event => {
|
ipcHandler = event => {
|
||||||
if (event.channel === "request-navigation") {
|
switch (event.channel) {
|
||||||
|
case "request-navigation": {
|
||||||
openExternal(event.args[0])
|
openExternal(event.args[0])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "context-menu": {
|
||||||
|
let articlePos = document.getElementById("article").getBoundingClientRect()
|
||||||
|
let [x, y] = event.args[0]
|
||||||
|
this.props.textMenu(event.args[1], [x + articlePos.x, y + articlePos.y])
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
popUpHandler = event => {
|
popUpHandler = event => {
|
||||||
openExternal(event.url)
|
openExternal(event.url)
|
||||||
}
|
}
|
||||||
|
navigationHandler = event => {
|
||||||
|
openExternal(event.url)
|
||||||
|
this.props.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount = () => {
|
componentDidMount = () => {
|
||||||
this.webview = document.getElementById("article")
|
this.webview = document.getElementById("article")
|
||||||
this.webview.addEventListener("ipc-message", this.ipcHandler)
|
this.webview.addEventListener("ipc-message", this.ipcHandler)
|
||||||
this.webview.addEventListener("new-window", this.popUpHandler)
|
this.webview.addEventListener("new-window", this.popUpHandler)
|
||||||
this.webview.addEventListener("will-navigate", this.props.dismiss)
|
this.webview.addEventListener("will-navigate", this.navigationHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount = () => {
|
componentWillUnmount = () => {
|
||||||
this.webview.removeEventListener("ipc-message", this.ipcHandler)
|
this.webview.removeEventListener("ipc-message", this.ipcHandler)
|
||||||
this.webview.removeEventListener("new-window", this.popUpHandler)
|
this.webview.removeEventListener("new-window", this.popUpHandler)
|
||||||
this.webview.removeEventListener("will-navigate", this.props.dismiss)
|
this.webview.removeEventListener("will-navigate", this.navigationHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
openInBrowser = () => {
|
openInBrowser = () => {
|
||||||
openExternal(this.props.item.link)
|
openExternal(this.props.item.link)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleWebpage = () => {
|
||||||
|
if (this.state.loadWebpage) {
|
||||||
|
this.setState({loadWebpage: false})
|
||||||
|
} else if (this.props.item.link.startsWith("https://") || this.props.item.link.startsWith("http://")) {
|
||||||
|
this.setState({loadWebpage: true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
articleView = () => "article/article.html?h=" + window.btoa(encodeURIComponent(renderToString(<>
|
articleView = () => "article/article.html?h=" + window.btoa(encodeURIComponent(renderToString(<>
|
||||||
<p className="title">{this.props.item.title}</p>
|
<p className="title">{this.props.item.title}</p>
|
||||||
|
<p className="date">{this.props.item.date.toLocaleString("zh-cn", {hour12: false})}</p>
|
||||||
<article dangerouslySetInnerHTML={{__html: this.props.item.content}}></article>
|
<article dangerouslySetInnerHTML={{__html: this.props.item.content}}></article>
|
||||||
</>))) + "&s=" + this.state.fontSize
|
</>))) + "&s=" + this.state.fontSize
|
||||||
|
|
||||||
@ -98,9 +123,15 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
iconProps={{iconName: "FavoriteStar"}} />
|
iconProps={{iconName: "FavoriteStar"}} />
|
||||||
<CommandBarButton
|
<CommandBarButton
|
||||||
title="字体大小"
|
title="字体大小"
|
||||||
|
disabled={this.state.loadWebpage}
|
||||||
iconProps={{iconName: "FontSize"}}
|
iconProps={{iconName: "FontSize"}}
|
||||||
menuIconProps={{style: {display: "none"}}}
|
menuIconProps={{style: {display: "none"}}}
|
||||||
menuProps={this.fontMenuProps()} />
|
menuProps={this.fontMenuProps()} />
|
||||||
|
<CommandBarButton
|
||||||
|
title="加载网页"
|
||||||
|
className={this.state.loadWebpage ? "active" : ""}
|
||||||
|
iconProps={{iconName: "Globe"}}
|
||||||
|
onClick={this.toggleWebpage} />
|
||||||
<CommandBarButton
|
<CommandBarButton
|
||||||
title="在浏览器中打开"
|
title="在浏览器中打开"
|
||||||
iconProps={{iconName: "NavigateExternalInline", style: {marginTop: -4}}}
|
iconProps={{iconName: "NavigateExternalInline", style: {marginTop: -4}}}
|
||||||
@ -115,8 +146,8 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<webview
|
<webview
|
||||||
id="article"
|
id="article"
|
||||||
src={this.articleView()}
|
src={this.state.loadWebpage ? this.props.item.link : this.articleView()}
|
||||||
preload="article/preload.js"
|
preload={this.state.loadWebpage ? null : "article/preload.js"}
|
||||||
partition="sandbox" />
|
partition="sandbox" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,7 @@ export interface CardProps {
|
|||||||
item: RSSItem
|
item: RSSItem
|
||||||
source: RSSSource
|
source: RSSSource
|
||||||
markRead: (item: RSSItem) => void
|
markRead: (item: RSSItem) => void
|
||||||
contextMenu: (item: RSSItem, e) => void
|
contextMenu: (feedId: FeedIdType, item: RSSItem, e) => void
|
||||||
showItem: (fid: FeedIdType, item: RSSItem) => void
|
showItem: (fid: FeedIdType, item: RSSItem) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ export class Card extends React.Component<CardProps> {
|
|||||||
this.openInBrowser()
|
this.openInBrowser()
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
this.props.contextMenu(this.props.item, e)
|
this.props.contextMenu(this.props.feedId, this.props.item, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,24 +1,38 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { clipboard } from "electron"
|
import { clipboard } from "electron"
|
||||||
import { openExternal } from "../scripts/utils"
|
import { openExternal, cutText, googleSearch } from "../scripts/utils"
|
||||||
import { ContextualMenu, IContextualMenuItem, ContextualMenuItemType } from "office-ui-fabric-react/lib/ContextualMenu"
|
import { ContextualMenu, IContextualMenuItem, ContextualMenuItemType, DirectionalHint } from "office-ui-fabric-react/lib/ContextualMenu"
|
||||||
import { ContextMenuType } from "../scripts/models/app"
|
import { ContextMenuType } from "../scripts/models/app"
|
||||||
import { RSSItem } from "../scripts/models/item"
|
import { RSSItem } from "../scripts/models/item"
|
||||||
import { ContextReduxProps } from "../containers/context-menu-container"
|
import { ContextReduxProps } from "../containers/context-menu-container"
|
||||||
|
import { FeedIdType } from "../scripts/models/feed"
|
||||||
|
|
||||||
export type ContextMenuProps = ContextReduxProps & {
|
export type ContextMenuProps = ContextReduxProps & {
|
||||||
type: ContextMenuType
|
type: ContextMenuType
|
||||||
event?: MouseEvent
|
event?: MouseEvent
|
||||||
|
position?: [number, number]
|
||||||
item?: RSSItem
|
item?: RSSItem
|
||||||
markRead: Function
|
feedId?: FeedIdType
|
||||||
markUnread: Function
|
text?: string
|
||||||
close: Function
|
showItem: (feedId: FeedIdType, item: RSSItem) => void
|
||||||
|
markRead: (item: RSSItem) => void
|
||||||
|
markUnread: (item: RSSItem) => void
|
||||||
|
close: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ContextMenu extends React.Component<ContextMenuProps> {
|
export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||||
getItems = (): IContextualMenuItem[] => {
|
getItems = (): IContextualMenuItem[] => {
|
||||||
switch (this.props.type) {
|
switch (this.props.type) {
|
||||||
case ContextMenuType.Item: return [
|
case ContextMenuType.Item: return [
|
||||||
|
{
|
||||||
|
key: "showItem",
|
||||||
|
text: "阅读",
|
||||||
|
iconProps: { iconName: "TextDocument" },
|
||||||
|
onClick: () => {
|
||||||
|
this.props.markRead(this.props.item)
|
||||||
|
this.props.showItem(this.props.feedId, this.props.item)
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "openInBrowser",
|
key: "openInBrowser",
|
||||||
text: "在浏览器中打开",
|
text: "在浏览器中打开",
|
||||||
@ -32,7 +46,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
? {
|
? {
|
||||||
key: "markAsUnread",
|
key: "markAsUnread",
|
||||||
text: "标为未读",
|
text: "标为未读",
|
||||||
iconProps: { iconName: "StatusCircleInner", style: { fontSize: 12, textAlign: "center" } },
|
iconProps: { iconName: "RadioBtnOn", style: { fontSize: 14, textAlign: "center" } },
|
||||||
onClick: () => { this.props.markUnread(this.props.item) }
|
onClick: () => { this.props.markUnread(this.props.item) }
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
@ -60,6 +74,20 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
onClick: () => { clipboard.writeText(this.props.item.link) }
|
onClick: () => { clipboard.writeText(this.props.item.link) }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
case ContextMenuType.Text: return [
|
||||||
|
{
|
||||||
|
key: "copyText",
|
||||||
|
text: "复制",
|
||||||
|
iconProps: { iconName: "Copy" },
|
||||||
|
onClick: () => { clipboard.writeText(this.props.text) }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "searchText",
|
||||||
|
text: `使用Google搜索“${cutText(this.props.text, 15)}”`,
|
||||||
|
iconProps: { iconName: "Search" },
|
||||||
|
onClick: () => { googleSearch(this.props.text) }
|
||||||
|
}
|
||||||
|
]
|
||||||
default: return []
|
default: return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,9 +95,10 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
render() {
|
render() {
|
||||||
return this.props.type == ContextMenuType.Hidden ? null : (
|
return this.props.type == ContextMenuType.Hidden ? null : (
|
||||||
<ContextualMenu
|
<ContextualMenu
|
||||||
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
items={this.getItems()}
|
items={this.getItems()}
|
||||||
target={this.props.event}
|
target={this.props.event || this.props.position && {left: this.props.position[0], top: this.props.position[1]}}
|
||||||
onDismiss={() => this.props.close()} />
|
onDismiss={this.props.close} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ type FeedProps = FeedReduxProps & {
|
|||||||
items: RSSItem[]
|
items: RSSItem[]
|
||||||
sourceMap: Object
|
sourceMap: Object
|
||||||
markRead: (item: RSSItem) => void
|
markRead: (item: RSSItem) => void
|
||||||
contextMenu: (item: RSSItem, e) => void
|
contextMenu: (feedId: FeedIdType, item: RSSItem, e) => void
|
||||||
loadMore: (feed: RSSFeed) => void
|
loadMore: (feed: RSSFeed) => void
|
||||||
showItem: (fid: FeedIdType, item: RSSItem) => void
|
showItem: (fid: FeedIdType, item: RSSItem) => void
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { ContextMenuContainer } from "../containers/context-menu-container"
|
import { ContextMenuContainer } from "../containers/context-menu-container"
|
||||||
import { closeContextMenu } from "../scripts/models/app"
|
import { closeContextMenu, openTextMenu } from "../scripts/models/app"
|
||||||
import PageContainer from "../containers/page-container"
|
import PageContainer from "../containers/page-container"
|
||||||
import MenuContainer from "../containers/menu-container"
|
import MenuContainer from "../containers/menu-container"
|
||||||
import NavContainer from "../containers/nav-container"
|
import NavContainer from "../containers/nav-container"
|
||||||
@ -9,7 +9,12 @@ import LogMenuContainer from "../containers/log-menu-container"
|
|||||||
import SettingsContainer from "../containers/settings-container"
|
import SettingsContainer from "../containers/settings-container"
|
||||||
|
|
||||||
const Root = ({ dispatch }) => (
|
const Root = ({ dispatch }) => (
|
||||||
<div id="root" onMouseDown={() => dispatch(closeContextMenu())}>
|
<div id="root"
|
||||||
|
onMouseDown={() => dispatch(closeContextMenu())}
|
||||||
|
onContextMenu={event => {
|
||||||
|
let text = document.getSelection().toString()
|
||||||
|
if (text) dispatch(openTextMenu(text, [event.clientX, event.clientY]))
|
||||||
|
}}>
|
||||||
<NavContainer />
|
<NavContainer />
|
||||||
<PageContainer />
|
<PageContainer />
|
||||||
<LogMenuContainer />
|
<LogMenuContainer />
|
||||||
|
@ -5,6 +5,7 @@ import { RSSItem, markUnread, markRead } from "../scripts/models/item"
|
|||||||
import { AppDispatch } from "../scripts/utils"
|
import { AppDispatch } from "../scripts/utils"
|
||||||
import { dismissItem } from "../scripts/models/page"
|
import { dismissItem } from "../scripts/models/page"
|
||||||
import Article from "../components/article"
|
import Article from "../components/article"
|
||||||
|
import { openTextMenu } from "../scripts/models/app"
|
||||||
|
|
||||||
type ArticleContainerProps = {
|
type ArticleContainerProps = {
|
||||||
itemId: number
|
itemId: number
|
||||||
@ -26,7 +27,8 @@ const makeMapStateToProps = () => {
|
|||||||
const mapDispatchToProps = (dispatch: AppDispatch) => {
|
const mapDispatchToProps = (dispatch: AppDispatch) => {
|
||||||
return {
|
return {
|
||||||
dismiss: () => dispatch(dismissItem()),
|
dismiss: () => dispatch(dismissItem()),
|
||||||
toggleHasRead: (item: RSSItem) => dispatch(item.hasRead ? markUnread(item) : markRead(item))
|
toggleHasRead: (item: RSSItem) => dispatch(item.hasRead ? markUnread(item) : markRead(item)),
|
||||||
|
textMenu: (text: string, position: [number, number]) => dispatch(openTextMenu(text, position))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ import { RootState } from "../scripts/reducer"
|
|||||||
import { ContextMenuType, closeContextMenu } from "../scripts/models/app"
|
import { ContextMenuType, closeContextMenu } from "../scripts/models/app"
|
||||||
import { ContextMenu } from "../components/context-menu"
|
import { ContextMenu } from "../components/context-menu"
|
||||||
import { RSSItem, markRead, markUnread } from "../scripts/models/item"
|
import { RSSItem, markRead, markUnread } from "../scripts/models/item"
|
||||||
|
import { showItem } from "../scripts/models/page"
|
||||||
|
import { FeedIdType } from "../scripts/models/feed"
|
||||||
|
|
||||||
const getContext = (state: RootState) => state.app.contextMenu
|
const getContext = (state: RootState) => state.app.contextMenu
|
||||||
|
|
||||||
@ -14,7 +16,13 @@ const mapStateToProps = createSelector(
|
|||||||
case ContextMenuType.Item: return {
|
case ContextMenuType.Item: return {
|
||||||
type: context.type,
|
type: context.type,
|
||||||
event: context.event,
|
event: context.event,
|
||||||
item: context.target as RSSItem
|
item: context.target[0],
|
||||||
|
feedId: context.target[1]
|
||||||
|
}
|
||||||
|
case ContextMenuType.Text: return {
|
||||||
|
type: context.type,
|
||||||
|
position: context.position,
|
||||||
|
text: context.target as string
|
||||||
}
|
}
|
||||||
default: return { type: ContextMenuType.Hidden }
|
default: return { type: ContextMenuType.Hidden }
|
||||||
}
|
}
|
||||||
@ -23,8 +31,9 @@ const mapStateToProps = createSelector(
|
|||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
markRead: item => dispatch(markRead(item)),
|
showItem: (feedId: FeedIdType, item: RSSItem) => dispatch(showItem(feedId, item)),
|
||||||
markUnread: item => dispatch(markUnread(item)),
|
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
||||||
|
markUnread: (item: RSSItem) => dispatch(markUnread(item)),
|
||||||
close: () => dispatch(closeContextMenu())
|
close: () => dispatch(closeContextMenu())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ const makeMapStateToProps = () => {
|
|||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
||||||
contextMenu: (item: RSSItem, e) => dispatch(openItemMenu(item, e)),
|
contextMenu: (feedId: FeedIdType, item: RSSItem, e) => dispatch(openItemMenu(item, feedId, e)),
|
||||||
loadMore: (feed: RSSFeed) => dispatch(loadMore(feed)),
|
loadMore: (feed: RSSFeed) => dispatch(loadMore(feed)),
|
||||||
showItem: (fid: FeedIdType, item: RSSItem) => dispatch(showItem(fid, item))
|
showItem: (fid: FeedIdType, item: RSSItem) => dispatch(showItem(fid, item))
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { app, ipcMain, BrowserWindow } from "electron"
|
import { app, ipcMain, BrowserWindow, Menu } from "electron"
|
||||||
import windowStateKeeper = require("electron-window-state")
|
import windowStateKeeper = require("electron-window-state")
|
||||||
|
|
||||||
let mainWindow: BrowserWindow
|
let mainWindow: BrowserWindow
|
||||||
@ -29,6 +29,8 @@ function createWindow() {
|
|||||||
mainWindow.webContents.openDevTools()
|
mainWindow.webContents.openDevTools()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Menu.setApplicationMenu(null)
|
||||||
|
|
||||||
app.on('ready', createWindow)
|
app.on('ready', createWindow)
|
||||||
|
|
||||||
app.on('window-all-closed', function () {
|
app.on('window-all-closed', function () {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { RSSSource, INIT_SOURCES, SourceActionTypes, ADD_SOURCE, UPDATE_SOURCE, DELETE_SOURCE } from "./source"
|
import { RSSSource, INIT_SOURCES, SourceActionTypes, ADD_SOURCE, UPDATE_SOURCE, DELETE_SOURCE } from "./source"
|
||||||
import { RSSItem, ItemActionTypes, FETCH_ITEMS } from "./item"
|
import { RSSItem, ItemActionTypes, FETCH_ITEMS } from "./item"
|
||||||
import { ActionStatus, AppThunk, getWindowBreakpoint } from "../utils"
|
import { ActionStatus, AppThunk, getWindowBreakpoint } from "../utils"
|
||||||
import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed"
|
import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds, FeedIdType } from "./feed"
|
||||||
import { SourceGroupActionTypes, UPDATE_SOURCE_GROUP, ADD_SOURCE_TO_GROUP, DELETE_SOURCE_GROUP, REMOVE_SOURCE_FROM_GROUP } from "./group"
|
import { SourceGroupActionTypes, UPDATE_SOURCE_GROUP, ADD_SOURCE_TO_GROUP, DELETE_SOURCE_GROUP, REMOVE_SOURCE_FROM_GROUP } from "./group"
|
||||||
import { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles } from "./page"
|
import { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles } from "./page"
|
||||||
|
|
||||||
export enum ContextMenuType {
|
export enum ContextMenuType {
|
||||||
Hidden, Item
|
Hidden, Item, Text
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AppLogType {
|
export enum AppLogType {
|
||||||
@ -50,7 +50,8 @@ export class AppState {
|
|||||||
contextMenu: {
|
contextMenu: {
|
||||||
type: ContextMenuType,
|
type: ContextMenuType,
|
||||||
event?: MouseEvent | string,
|
event?: MouseEvent | string,
|
||||||
target?: RSSItem | RSSSource
|
position?: [number, number],
|
||||||
|
target?: [RSSItem, FeedIdType] | RSSSource | string
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -62,6 +63,7 @@ export class AppState {
|
|||||||
|
|
||||||
export const CLOSE_CONTEXT_MENU = "CLOSE_CONTEXT_MENU"
|
export const CLOSE_CONTEXT_MENU = "CLOSE_CONTEXT_MENU"
|
||||||
export const OPEN_ITEM_MENU = "OPEN_ITEM_MENU"
|
export const OPEN_ITEM_MENU = "OPEN_ITEM_MENU"
|
||||||
|
export const OPEN_TEXT_MENU = "OPEN_TEXT_MENU"
|
||||||
|
|
||||||
interface CloseContextMenuAction {
|
interface CloseContextMenuAction {
|
||||||
type: typeof CLOSE_CONTEXT_MENU
|
type: typeof CLOSE_CONTEXT_MENU
|
||||||
@ -71,9 +73,16 @@ interface OpenItemMenuAction {
|
|||||||
type: typeof OPEN_ITEM_MENU
|
type: typeof OPEN_ITEM_MENU
|
||||||
event: MouseEvent
|
event: MouseEvent
|
||||||
item: RSSItem
|
item: RSSItem
|
||||||
|
feedId: FeedIdType
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ContextMenuActionTypes = CloseContextMenuAction | OpenItemMenuAction
|
interface OpenTextMenuAction {
|
||||||
|
type: typeof OPEN_TEXT_MENU
|
||||||
|
position: [number, number]
|
||||||
|
item: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ContextMenuActionTypes = CloseContextMenuAction | OpenItemMenuAction | OpenTextMenuAction
|
||||||
|
|
||||||
export const TOGGLE_LOGS = "TOGGLE_LOGS"
|
export const TOGGLE_LOGS = "TOGGLE_LOGS"
|
||||||
export interface LogMenuActionType { type: typeof TOGGLE_LOGS }
|
export interface LogMenuActionType { type: typeof TOGGLE_LOGS }
|
||||||
@ -95,11 +104,20 @@ export function closeContextMenu(): ContextMenuActionTypes {
|
|||||||
return { type: CLOSE_CONTEXT_MENU }
|
return { type: CLOSE_CONTEXT_MENU }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openItemMenu(item: RSSItem, event: React.MouseEvent): ContextMenuActionTypes {
|
export function openItemMenu(item: RSSItem, feedId: FeedIdType, event: React.MouseEvent): ContextMenuActionTypes {
|
||||||
return {
|
return {
|
||||||
type: OPEN_ITEM_MENU,
|
type: OPEN_ITEM_MENU,
|
||||||
event: event.nativeEvent,
|
event: event.nativeEvent,
|
||||||
item: item
|
item: item,
|
||||||
|
feedId: feedId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openTextMenu(text: string, position: [number, number]): ContextMenuActionTypes {
|
||||||
|
return {
|
||||||
|
type: OPEN_TEXT_MENU,
|
||||||
|
position: position,
|
||||||
|
item: text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,6 +263,14 @@ export function appReducer(
|
|||||||
contextMenu: {
|
contextMenu: {
|
||||||
type: ContextMenuType.Item,
|
type: ContextMenuType.Item,
|
||||||
event: action.event,
|
event: action.event,
|
||||||
|
target: [action.item, action.feedId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case OPEN_TEXT_MENU: return {
|
||||||
|
...state,
|
||||||
|
contextMenu: {
|
||||||
|
type: ContextMenuType.Text,
|
||||||
|
position: action.position,
|
||||||
target: action.item
|
target: action.item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,3 +83,9 @@ export const urlTest = (s: string) =>
|
|||||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi.test(s)
|
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi.test(s)
|
||||||
|
|
||||||
export const getWindowBreakpoint = () => remote.getCurrentWindow().getSize()[0] >= 1441
|
export const getWindowBreakpoint = () => remote.getCurrentWindow().getSize()[0] >= 1441
|
||||||
|
|
||||||
|
export const cutText = (s: string, length: number) => {
|
||||||
|
return (s.length <= length) ? s : s.slice(0, length) + "…"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const googleSearch = (text: string) => openExternal("https://www.google.com/search?q=" + encodeURIComponent(text))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user