mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-04-10 16:41:03 +02:00
refactor context menu as function component
This commit is contained in:
parent
ba9edbd65b
commit
5ecf189ed6
@ -13,38 +13,25 @@ import {
|
|||||||
ContextualMenuItemType,
|
ContextualMenuItemType,
|
||||||
DirectionalHint,
|
DirectionalHint,
|
||||||
} from "office-ui-fabric-react/lib/ContextualMenu"
|
} from "office-ui-fabric-react/lib/ContextualMenu"
|
||||||
import { ContextMenuType } from "../scripts/models/app"
|
import { closeContextMenu, ContextMenuType } from "../scripts/models/app"
|
||||||
import { RSSItem } from "../scripts/models/item"
|
import {
|
||||||
import { ContextReduxProps } from "../containers/context-menu-container"
|
markAllRead,
|
||||||
|
markRead,
|
||||||
|
markUnread,
|
||||||
|
RSSItem,
|
||||||
|
toggleHidden,
|
||||||
|
toggleStarred,
|
||||||
|
} from "../scripts/models/item"
|
||||||
import { ViewType, ImageCallbackTypes, ViewConfigs } from "../schema-types"
|
import { ViewType, ImageCallbackTypes, ViewConfigs } from "../schema-types"
|
||||||
import { FilterType } from "../scripts/models/feed"
|
import { FilterType } from "../scripts/models/feed"
|
||||||
|
import { useAppDispatch, useAppSelector } from "../scripts/reducer"
|
||||||
export type ContextMenuProps = ContextReduxProps & {
|
import {
|
||||||
type: ContextMenuType
|
setViewConfigs,
|
||||||
event?: MouseEvent | string
|
showItem,
|
||||||
position?: [number, number]
|
switchFilter,
|
||||||
item?: RSSItem
|
switchView,
|
||||||
feedId?: string
|
toggleFilter,
|
||||||
text?: string
|
} from "../scripts/models/page"
|
||||||
url?: string
|
|
||||||
viewType?: ViewType
|
|
||||||
viewConfigs?: ViewConfigs
|
|
||||||
filter?: FilterType
|
|
||||||
sids?: number[]
|
|
||||||
showItem: (feedId: string, item: RSSItem) => void
|
|
||||||
markRead: (item: RSSItem) => void
|
|
||||||
markUnread: (item: RSSItem) => void
|
|
||||||
toggleStarred: (item: RSSItem) => void
|
|
||||||
toggleHidden: (item: RSSItem) => void
|
|
||||||
switchView: (viewType: ViewType) => void
|
|
||||||
setViewConfigs: (configs: ViewConfigs) => void
|
|
||||||
switchFilter: (filter: FilterType) => void
|
|
||||||
toggleFilter: (filter: FilterType) => void
|
|
||||||
markAllRead: (sids?: number[], date?: Date, before?: boolean) => void
|
|
||||||
fetchItems: (sids: number[]) => void
|
|
||||||
settings: (sids: number[]) => void
|
|
||||||
close: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const shareSubmenu = (item: RSSItem): IContextualMenuItem[] => [
|
export const shareSubmenu = (item: RSSItem): IContextualMenuItem[] => [
|
||||||
{ key: "qr", url: item.link, onRender: renderShareQR },
|
{ key: "qr", url: item.link, onRender: renderShareQR },
|
||||||
@ -69,21 +56,42 @@ function getSearchItem(text: string): IContextualMenuItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ContextMenu extends React.Component<ContextMenuProps> {
|
export function ContextMenu() {
|
||||||
getItems = (): IContextualMenuItem[] => {
|
const { type } = useAppSelector(state => state.app.contextMenu)
|
||||||
switch (this.props.type) {
|
|
||||||
|
switch (type) {
|
||||||
|
case ContextMenuType.Hidden:
|
||||||
|
return null
|
||||||
case ContextMenuType.Item:
|
case ContextMenuType.Item:
|
||||||
return [
|
return <ItemContextMenu />
|
||||||
|
case ContextMenuType.Text:
|
||||||
|
return <TextContextMenu />
|
||||||
|
case ContextMenuType.Image:
|
||||||
|
return <ImageContextMenu />
|
||||||
|
case ContextMenuType.View:
|
||||||
|
return <ViewContextMenu />
|
||||||
|
case ContextMenuType.Group:
|
||||||
|
return <GroupContextMenu />
|
||||||
|
case ContextMenuType.MarkRead:
|
||||||
|
return <MarkReadContextMenu />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ItemContextMenu() {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const viewConfigs = useAppSelector(state => state.page.viewConfigs)
|
||||||
|
const target = useAppSelector(state => state.app.contextMenu.target)
|
||||||
|
const item = target[0] as RSSItem
|
||||||
|
const feedId = target[1] as string
|
||||||
|
|
||||||
|
const menuItems: IContextualMenuItem[] = [
|
||||||
{
|
{
|
||||||
key: "showItem",
|
key: "showItem",
|
||||||
text: intl.get("context.read"),
|
text: intl.get("context.read"),
|
||||||
iconProps: { iconName: "TextDocument" },
|
iconProps: { iconName: "TextDocument" },
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
this.props.markRead(this.props.item)
|
dispatch(markRead(item))
|
||||||
this.props.showItem(
|
dispatch(showItem(feedId, item))
|
||||||
this.props.feedId,
|
|
||||||
this.props.item
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -91,28 +99,27 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
text: intl.get("openExternal"),
|
text: intl.get("openExternal"),
|
||||||
iconProps: { iconName: "NavigateExternalInline" },
|
iconProps: { iconName: "NavigateExternalInline" },
|
||||||
onClick: e => {
|
onClick: e => {
|
||||||
this.props.markRead(this.props.item)
|
dispatch(markRead(item))
|
||||||
window.utils.openExternal(
|
window.utils.openExternal(item.link, platformCtrl(e))
|
||||||
this.props.item.link,
|
|
||||||
platformCtrl(e)
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "markAsRead",
|
key: "markAsRead",
|
||||||
text: this.props.item.hasRead
|
text: item.hasRead
|
||||||
? intl.get("article.markUnread")
|
? intl.get("article.markUnread")
|
||||||
: intl.get("article.markRead"),
|
: intl.get("article.markRead"),
|
||||||
iconProps: this.props.item.hasRead
|
iconProps: item.hasRead
|
||||||
? {
|
? {
|
||||||
iconName: "RadioBtnOn",
|
iconName: "RadioBtnOn",
|
||||||
style: { fontSize: 14, textAlign: "center" },
|
style: { fontSize: 14, textAlign: "center" },
|
||||||
}
|
}
|
||||||
: { iconName: "StatusCircleRing" },
|
: { iconName: "StatusCircleRing" },
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (this.props.item.hasRead)
|
if (item.hasRead) {
|
||||||
this.props.markUnread(this.props.item)
|
dispatch(markUnread(item))
|
||||||
else this.props.markRead(this.props.item)
|
} else {
|
||||||
|
dispatch(markRead(item))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
split: true,
|
split: true,
|
||||||
subMenuProps: {
|
subMenuProps: {
|
||||||
@ -124,11 +131,9 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
iconName: "Down",
|
iconName: "Down",
|
||||||
style: { fontSize: 14 },
|
style: { fontSize: 14 },
|
||||||
},
|
},
|
||||||
onClick: () =>
|
onClick: () => {
|
||||||
this.props.markAllRead(
|
dispatch(markAllRead(null, item.date))
|
||||||
null,
|
},
|
||||||
this.props.item.date
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "markAbove",
|
key: "markAbove",
|
||||||
@ -137,40 +142,35 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
iconName: "Up",
|
iconName: "Up",
|
||||||
style: { fontSize: 14 },
|
style: { fontSize: 14 },
|
||||||
},
|
},
|
||||||
onClick: () =>
|
onClick: () => {
|
||||||
this.props.markAllRead(
|
dispatch(markAllRead(null, item.date, false))
|
||||||
null,
|
},
|
||||||
this.props.item.date,
|
|
||||||
false
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "toggleStarred",
|
key: "toggleStarred",
|
||||||
text: this.props.item.starred
|
text: item.starred
|
||||||
? intl.get("article.unstar")
|
? intl.get("article.unstar")
|
||||||
: intl.get("article.star"),
|
: intl.get("article.star"),
|
||||||
iconProps: {
|
iconProps: {
|
||||||
iconName: this.props.item.starred
|
iconName: item.starred ? "FavoriteStar" : "FavoriteStarFill",
|
||||||
? "FavoriteStar"
|
|
||||||
: "FavoriteStarFill",
|
|
||||||
},
|
},
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
this.props.toggleStarred(this.props.item)
|
dispatch(toggleStarred(item))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "toggleHidden",
|
key: "toggleHidden",
|
||||||
text: this.props.item.hidden
|
text: item.hidden
|
||||||
? intl.get("article.unhide")
|
? intl.get("article.unhide")
|
||||||
: intl.get("article.hide"),
|
: intl.get("article.hide"),
|
||||||
iconProps: {
|
iconProps: {
|
||||||
iconName: this.props.item.hidden ? "View" : "Hide3",
|
iconName: item.hidden ? "View" : "Hide3",
|
||||||
},
|
},
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
this.props.toggleHidden(this.props.item)
|
dispatch(toggleHidden(item))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -182,24 +182,24 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
text: intl.get("context.share"),
|
text: intl.get("context.share"),
|
||||||
iconProps: { iconName: "Share" },
|
iconProps: { iconName: "Share" },
|
||||||
subMenuProps: {
|
subMenuProps: {
|
||||||
items: shareSubmenu(this.props.item),
|
items: shareSubmenu(item),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "copyTitle",
|
key: "copyTitle",
|
||||||
text: intl.get("context.copyTitle"),
|
text: intl.get("context.copyTitle"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.utils.writeClipboard(this.props.item.title)
|
window.utils.writeClipboard(item.title)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "copyURL",
|
key: "copyURL",
|
||||||
text: intl.get("context.copyURL"),
|
text: intl.get("context.copyURL"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.utils.writeClipboard(this.props.item.link)
|
window.utils.writeClipboard(item.link)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...(this.props.viewConfigs !== undefined
|
...(viewConfigs !== undefined
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: "divider_2",
|
key: "divider_2",
|
||||||
@ -212,50 +212,46 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: "showCover",
|
key: "showCover",
|
||||||
text: intl.get(
|
text: intl.get("context.showCover"),
|
||||||
"context.showCover"
|
|
||||||
),
|
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked: Boolean(
|
checked: Boolean(
|
||||||
this.props.viewConfigs &
|
viewConfigs & ViewConfigs.ShowCover
|
||||||
ViewConfigs.ShowCover
|
|
||||||
),
|
),
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
this.props.setViewConfigs(
|
dispatch(
|
||||||
this.props.viewConfigs ^
|
setViewConfigs(
|
||||||
|
viewConfigs ^
|
||||||
ViewConfigs.ShowCover
|
ViewConfigs.ShowCover
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "showSnippet",
|
key: "showSnippet",
|
||||||
text: intl.get(
|
text: intl.get("context.showSnippet"),
|
||||||
"context.showSnippet"
|
|
||||||
),
|
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked: Boolean(
|
checked: Boolean(
|
||||||
this.props.viewConfigs &
|
viewConfigs & ViewConfigs.ShowSnippet
|
||||||
ViewConfigs.ShowSnippet
|
|
||||||
),
|
),
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
this.props.setViewConfigs(
|
dispatch(
|
||||||
this.props.viewConfigs ^
|
setViewConfigs(
|
||||||
|
viewConfigs ^
|
||||||
ViewConfigs.ShowSnippet
|
ViewConfigs.ShowSnippet
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "fadeRead",
|
key: "fadeRead",
|
||||||
text: intl.get(
|
text: intl.get("context.fadeRead"),
|
||||||
"context.fadeRead"
|
|
||||||
),
|
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked: Boolean(
|
checked: Boolean(
|
||||||
this.props.viewConfigs &
|
viewConfigs & ViewConfigs.FadeRead
|
||||||
ViewConfigs.FadeRead
|
|
||||||
),
|
),
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
this.props.setViewConfigs(
|
dispatch(
|
||||||
this.props.viewConfigs ^
|
setViewConfigs(
|
||||||
ViewConfigs.FadeRead
|
viewConfigs ^ ViewConfigs.FadeRead
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -264,26 +260,35 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
]
|
]
|
||||||
case ContextMenuType.Text: {
|
return <ContextMenuBase menuItems={menuItems} />
|
||||||
const items: IContextualMenuItem[] = this.props.text
|
}
|
||||||
|
|
||||||
|
function TextContextMenu() {
|
||||||
|
const target = useAppSelector(state => state.app.contextMenu.target) as [
|
||||||
|
string,
|
||||||
|
string
|
||||||
|
]
|
||||||
|
const text = target[0]
|
||||||
|
const url = target[1]
|
||||||
|
const menuItems: IContextualMenuItem[] = text
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: "copyText",
|
key: "copyText",
|
||||||
text: intl.get("context.copy"),
|
text: intl.get("context.copy"),
|
||||||
iconProps: { iconName: "Copy" },
|
iconProps: { iconName: "Copy" },
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.utils.writeClipboard(this.props.text)
|
window.utils.writeClipboard(text)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getSearchItem(this.props.text),
|
getSearchItem(text),
|
||||||
]
|
]
|
||||||
: []
|
: []
|
||||||
if (this.props.url) {
|
if (url) {
|
||||||
items.push({
|
menuItems.push({
|
||||||
key: "urlSection",
|
key: "urlSection",
|
||||||
itemType: ContextualMenuItemType.Section,
|
itemType: ContextualMenuItemType.Section,
|
||||||
sectionProps: {
|
sectionProps: {
|
||||||
topDivider: items.length > 0,
|
topDivider: menuItems.length > 0,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: "openInBrowser",
|
key: "openInBrowser",
|
||||||
@ -292,10 +297,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
iconName: "NavigateExternalInline",
|
iconName: "NavigateExternalInline",
|
||||||
},
|
},
|
||||||
onClick: e => {
|
onClick: e => {
|
||||||
window.utils.openExternal(
|
window.utils.openExternal(url, platformCtrl(e))
|
||||||
this.props.url,
|
|
||||||
platformCtrl(e)
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -303,19 +305,18 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
text: intl.get("context.copyURL"),
|
text: intl.get("context.copyURL"),
|
||||||
iconProps: { iconName: "Link" },
|
iconProps: { iconName: "Link" },
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.utils.writeClipboard(
|
window.utils.writeClipboard(url)
|
||||||
this.props.url
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return items
|
return <ContextMenuBase menuItems={menuItems} />
|
||||||
}
|
}
|
||||||
case ContextMenuType.Image:
|
|
||||||
return [
|
function ImageContextMenu() {
|
||||||
|
const menuItems: IContextualMenuItem[] = [
|
||||||
{
|
{
|
||||||
key: "openInBrowser",
|
key: "openInBrowser",
|
||||||
text: intl.get("openExternal"),
|
text: intl.get("openExternal"),
|
||||||
@ -326,9 +327,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
ImageCallbackTypes.OpenExternalBg
|
ImageCallbackTypes.OpenExternalBg
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
window.utils.imageCallback(
|
window.utils.imageCallback(ImageCallbackTypes.OpenExternal)
|
||||||
ImageCallbackTypes.OpenExternal
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -337,9 +336,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
text: intl.get("context.saveImageAs"),
|
text: intl.get("context.saveImageAs"),
|
||||||
iconProps: { iconName: "SaveTemplate" },
|
iconProps: { iconName: "SaveTemplate" },
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.utils.imageCallback(
|
window.utils.imageCallback(ImageCallbackTypes.SaveAs)
|
||||||
ImageCallbackTypes.SaveAs
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -355,14 +352,19 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
text: intl.get("context.copyImageURL"),
|
text: intl.get("context.copyImageURL"),
|
||||||
iconProps: { iconName: "Link" },
|
iconProps: { iconName: "Link" },
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.utils.imageCallback(
|
window.utils.imageCallback(ImageCallbackTypes.CopyLink)
|
||||||
ImageCallbackTypes.CopyLink
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
case ContextMenuType.View:
|
return <ContextMenuBase menuItems={menuItems} />
|
||||||
return [
|
}
|
||||||
|
|
||||||
|
function ViewContextMenu() {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const viewType = useAppSelector(state => state.page.viewType)
|
||||||
|
const filter = useAppSelector(state => state.page.filter.type)
|
||||||
|
|
||||||
|
const menuItems: IContextualMenuItem[] = [
|
||||||
{
|
{
|
||||||
key: "section_1",
|
key: "section_1",
|
||||||
itemType: ContextualMenuItemType.Section,
|
itemType: ContextualMenuItemType.Section,
|
||||||
@ -375,44 +377,32 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
text: intl.get("context.cardView"),
|
text: intl.get("context.cardView"),
|
||||||
iconProps: { iconName: "GridViewMedium" },
|
iconProps: { iconName: "GridViewMedium" },
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked:
|
checked: viewType === ViewType.Cards,
|
||||||
this.props.viewType === ViewType.Cards,
|
onClick: () => dispatch(switchView(ViewType.Cards)),
|
||||||
onClick: () =>
|
|
||||||
this.props.switchView(ViewType.Cards),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "listView",
|
key: "listView",
|
||||||
text: intl.get("context.listView"),
|
text: intl.get("context.listView"),
|
||||||
iconProps: { iconName: "BacklogList" },
|
iconProps: { iconName: "BacklogList" },
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked:
|
checked: viewType === ViewType.List,
|
||||||
this.props.viewType === ViewType.List,
|
onClick: () => dispatch(switchView(ViewType.List)),
|
||||||
onClick: () =>
|
|
||||||
this.props.switchView(ViewType.List),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "magazineView",
|
key: "magazineView",
|
||||||
text: intl.get("context.magazineView"),
|
text: intl.get("context.magazineView"),
|
||||||
iconProps: { iconName: "Articles" },
|
iconProps: { iconName: "Articles" },
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked:
|
checked: viewType === ViewType.Magazine,
|
||||||
this.props.viewType ===
|
onClick: () => dispatch(switchView(ViewType.Magazine)),
|
||||||
ViewType.Magazine,
|
|
||||||
onClick: () =>
|
|
||||||
this.props.switchView(
|
|
||||||
ViewType.Magazine
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "compactView",
|
key: "compactView",
|
||||||
text: intl.get("context.compactView"),
|
text: intl.get("context.compactView"),
|
||||||
iconProps: { iconName: "BulletedList" },
|
iconProps: { iconName: "BulletedList" },
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked:
|
checked: viewType === ViewType.Compact,
|
||||||
this.props.viewType ===
|
onClick: () => dispatch(switchView(ViewType.Compact)),
|
||||||
ViewType.Compact,
|
|
||||||
onClick: () =>
|
|
||||||
this.props.switchView(ViewType.Compact),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -430,13 +420,10 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
iconProps: { iconName: "ClearFilter" },
|
iconProps: { iconName: "ClearFilter" },
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked:
|
checked:
|
||||||
(this.props.filter &
|
(filter & ~FilterType.Toggles) ==
|
||||||
~FilterType.Toggles) ==
|
|
||||||
FilterType.Default,
|
FilterType.Default,
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
this.props.switchFilter(
|
dispatch(switchFilter(FilterType.Default)),
|
||||||
FilterType.Default
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "unreadOnly",
|
key: "unreadOnly",
|
||||||
@ -450,13 +437,10 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
},
|
},
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked:
|
checked:
|
||||||
(this.props.filter &
|
(filter & ~FilterType.Toggles) ==
|
||||||
~FilterType.Toggles) ==
|
|
||||||
FilterType.UnreadOnly,
|
FilterType.UnreadOnly,
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
this.props.switchFilter(
|
dispatch(switchFilter(FilterType.UnreadOnly)),
|
||||||
FilterType.UnreadOnly
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "starredOnly",
|
key: "starredOnly",
|
||||||
@ -464,13 +448,10 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
iconProps: { iconName: "FavoriteStarFill" },
|
iconProps: { iconName: "FavoriteStarFill" },
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked:
|
checked:
|
||||||
(this.props.filter &
|
(filter & ~FilterType.Toggles) ==
|
||||||
~FilterType.Toggles) ==
|
|
||||||
FilterType.StarredOnly,
|
FilterType.StarredOnly,
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
this.props.switchFilter(
|
dispatch(switchFilter(FilterType.StarredOnly)),
|
||||||
FilterType.StarredOnly
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -493,28 +474,18 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
children: "Aa",
|
children: "Aa",
|
||||||
},
|
},
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked: !(
|
checked: !(filter & FilterType.CaseInsensitive),
|
||||||
this.props.filter &
|
|
||||||
FilterType.CaseInsensitive
|
|
||||||
),
|
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
this.props.toggleFilter(
|
dispatch(toggleFilter(FilterType.CaseInsensitive)),
|
||||||
FilterType.CaseInsensitive
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "fullSearch",
|
key: "fullSearch",
|
||||||
text: intl.get("context.fullSearch"),
|
text: intl.get("context.fullSearch"),
|
||||||
iconProps: { iconName: "Breadcrumb" },
|
iconProps: { iconName: "Breadcrumb" },
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked: Boolean(
|
checked: Boolean(filter & FilterType.FullSearch),
|
||||||
this.props.filter &
|
|
||||||
FilterType.FullSearch
|
|
||||||
),
|
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
this.props.toggleFilter(
|
dispatch(toggleFilter(FilterType.FullSearch)),
|
||||||
FilterType.FullSearch
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -523,36 +494,52 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
key: "showHidden",
|
key: "showHidden",
|
||||||
text: intl.get("context.showHidden"),
|
text: intl.get("context.showHidden"),
|
||||||
canCheck: true,
|
canCheck: true,
|
||||||
checked: Boolean(
|
checked: Boolean(filter & FilterType.ShowHidden),
|
||||||
this.props.filter & FilterType.ShowHidden
|
onClick: () => dispatch(toggleFilter(FilterType.ShowHidden)),
|
||||||
),
|
|
||||||
onClick: () =>
|
|
||||||
this.props.toggleFilter(FilterType.ShowHidden),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
case ContextMenuType.Group:
|
return <ContextMenuBase menuItems={menuItems} />
|
||||||
return [
|
}
|
||||||
|
|
||||||
|
function GroupContextMenu() {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const sids = useAppSelector(
|
||||||
|
state => state.app.contextMenu.target
|
||||||
|
) as number[]
|
||||||
|
|
||||||
|
const menuItems: IContextualMenuItem[] = [
|
||||||
{
|
{
|
||||||
key: "markAllRead",
|
key: "markAllRead",
|
||||||
text: intl.get("nav.markAllRead"),
|
text: intl.get("nav.markAllRead"),
|
||||||
iconProps: { iconName: "CheckMark" },
|
iconProps: { iconName: "CheckMark" },
|
||||||
onClick: () => this.props.markAllRead(this.props.sids),
|
onClick: () => {
|
||||||
|
dispatch(markAllRead(sids))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "refresh",
|
key: "refresh",
|
||||||
text: intl.get("nav.refresh"),
|
text: intl.get("nav.refresh"),
|
||||||
iconProps: { iconName: "Sync" },
|
iconProps: { iconName: "Sync" },
|
||||||
onClick: () => this.props.fetchItems(this.props.sids),
|
onClick: () => {
|
||||||
|
dispatch(markAllRead(sids))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "manage",
|
key: "manage",
|
||||||
text: intl.get("context.manageSources"),
|
text: intl.get("context.manageSources"),
|
||||||
iconProps: { iconName: "Settings" },
|
iconProps: { iconName: "Settings" },
|
||||||
onClick: () => this.props.settings(this.props.sids),
|
onClick: () => {
|
||||||
|
dispatch(markAllRead(sids))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
case ContextMenuType.MarkRead:
|
return <ContextMenuBase menuItems={menuItems} />
|
||||||
return [
|
}
|
||||||
|
|
||||||
|
function MarkReadContextMenu() {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const menuItems: IContextualMenuItem[] = [
|
||||||
{
|
{
|
||||||
key: "section_1",
|
key: "section_1",
|
||||||
itemType: ContextualMenuItemType.Section,
|
itemType: ContextualMenuItemType.Section,
|
||||||
@ -563,7 +550,9 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
key: "all",
|
key: "all",
|
||||||
text: intl.get("allArticles"),
|
text: intl.get("allArticles"),
|
||||||
iconProps: { iconName: "ReceiptCheck" },
|
iconProps: { iconName: "ReceiptCheck" },
|
||||||
onClick: () => this.props.markAllRead(),
|
onClick: () => {
|
||||||
|
dispatch(markAllRead())
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "1d",
|
key: "1d",
|
||||||
@ -571,7 +560,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
let date = new Date()
|
let date = new Date()
|
||||||
date.setTime(date.getTime() - 86400000)
|
date.setTime(date.getTime() - 86400000)
|
||||||
this.props.markAllRead(null, date)
|
dispatch(markAllRead(null, date))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -579,10 +568,8 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
text: intl.get("app.daysAgo", { days: 3 }),
|
text: intl.get("app.daysAgo", { days: 3 }),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
let date = new Date()
|
let date = new Date()
|
||||||
date.setTime(
|
date.setTime(date.getTime() - 3 * 86400000)
|
||||||
date.getTime() - 3 * 86400000
|
dispatch(markAllRead(null, date))
|
||||||
)
|
|
||||||
this.props.markAllRead(null, date)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -590,35 +577,35 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
text: intl.get("app.daysAgo", { days: 7 }),
|
text: intl.get("app.daysAgo", { days: 7 }),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
let date = new Date()
|
let date = new Date()
|
||||||
date.setTime(
|
date.setTime(date.getTime() - 7 * 86400000)
|
||||||
date.getTime() - 7 * 86400000
|
dispatch(markAllRead(null, date))
|
||||||
)
|
|
||||||
this.props.markAllRead(null, date)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
default:
|
return <ContextMenuBase menuItems={menuItems} />
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
function ContextMenuBase({
|
||||||
return this.props.type == ContextMenuType.Hidden ? null : (
|
menuItems,
|
||||||
|
}: Readonly<{ menuItems: IContextualMenuItem[] }>) {
|
||||||
|
const { event, position } = useAppSelector(state => state.app.contextMenu)
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
return (
|
||||||
<ContextualMenu
|
<ContextualMenu
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
items={this.getItems()}
|
items={menuItems}
|
||||||
target={
|
target={
|
||||||
this.props.event ||
|
event ||
|
||||||
(this.props.position && {
|
(position && {
|
||||||
left: this.props.position[0],
|
left: position[0],
|
||||||
top: this.props.position[1],
|
top: position[1],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onDismiss={this.props.close}
|
onDismiss={() => dispatch(closeContextMenu())}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
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 { closeContextMenu } from "../scripts/models/app"
|
import { closeContextMenu } 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"
|
||||||
@ -8,6 +7,7 @@ import NavContainer from "../containers/nav-container"
|
|||||||
import LogMenuContainer from "../containers/log-menu-container"
|
import LogMenuContainer from "../containers/log-menu-container"
|
||||||
import SettingsContainer from "../containers/settings-container"
|
import SettingsContainer from "../containers/settings-container"
|
||||||
import { RootState } from "../scripts/reducer"
|
import { RootState } from "../scripts/reducer"
|
||||||
|
import { ContextMenu } from "./context-menu"
|
||||||
|
|
||||||
const Root = ({ locale, dispatch }) =>
|
const Root = ({ locale, dispatch }) =>
|
||||||
locale && (
|
locale && (
|
||||||
@ -20,7 +20,7 @@ const Root = ({ locale, dispatch }) =>
|
|||||||
<LogMenuContainer />
|
<LogMenuContainer />
|
||||||
<MenuContainer />
|
<MenuContainer />
|
||||||
<SettingsContainer />
|
<SettingsContainer />
|
||||||
<ContextMenuContainer />
|
<ContextMenu />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,115 +0,0 @@
|
|||||||
import { connect } from "react-redux"
|
|
||||||
import { createSelector } from "reselect"
|
|
||||||
import { RootState } from "../scripts/reducer"
|
|
||||||
import {
|
|
||||||
ContextMenuType,
|
|
||||||
closeContextMenu,
|
|
||||||
toggleSettings,
|
|
||||||
} from "../scripts/models/app"
|
|
||||||
import { ContextMenu } from "../components/context-menu"
|
|
||||||
import {
|
|
||||||
RSSItem,
|
|
||||||
markRead,
|
|
||||||
markUnread,
|
|
||||||
toggleStarred,
|
|
||||||
toggleHidden,
|
|
||||||
markAllRead,
|
|
||||||
fetchItems,
|
|
||||||
} from "../scripts/models/item"
|
|
||||||
import {
|
|
||||||
showItem,
|
|
||||||
switchView,
|
|
||||||
switchFilter,
|
|
||||||
toggleFilter,
|
|
||||||
setViewConfigs,
|
|
||||||
} from "../scripts/models/page"
|
|
||||||
import { ViewType, ViewConfigs } from "../schema-types"
|
|
||||||
import { FilterType } from "../scripts/models/feed"
|
|
||||||
|
|
||||||
const getContext = (state: RootState) => state.app.contextMenu
|
|
||||||
const getViewType = (state: RootState) => state.page.viewType
|
|
||||||
const getFilter = (state: RootState) => state.page.filter
|
|
||||||
const getViewConfigs = (state: RootState) => state.page.viewConfigs
|
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
|
||||||
[getContext, getViewType, getFilter, getViewConfigs],
|
|
||||||
(context, viewType, filter, viewConfigs) => {
|
|
||||||
switch (context.type) {
|
|
||||||
case ContextMenuType.Item:
|
|
||||||
return {
|
|
||||||
type: context.type,
|
|
||||||
event: context.event,
|
|
||||||
viewConfigs: viewConfigs,
|
|
||||||
item: context.target[0],
|
|
||||||
feedId: context.target[1],
|
|
||||||
}
|
|
||||||
case ContextMenuType.Text:
|
|
||||||
return {
|
|
||||||
type: context.type,
|
|
||||||
position: context.position,
|
|
||||||
text: context.target[0],
|
|
||||||
url: context.target[1],
|
|
||||||
}
|
|
||||||
case ContextMenuType.View:
|
|
||||||
return {
|
|
||||||
type: context.type,
|
|
||||||
event: context.event,
|
|
||||||
viewType: viewType,
|
|
||||||
filter: filter.type,
|
|
||||||
}
|
|
||||||
case ContextMenuType.Group:
|
|
||||||
return {
|
|
||||||
type: context.type,
|
|
||||||
event: context.event,
|
|
||||||
sids: context.target,
|
|
||||||
}
|
|
||||||
case ContextMenuType.Image:
|
|
||||||
return {
|
|
||||||
type: context.type,
|
|
||||||
position: context.position,
|
|
||||||
}
|
|
||||||
case ContextMenuType.MarkRead:
|
|
||||||
return {
|
|
||||||
type: context.type,
|
|
||||||
event: context.event,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return { type: ContextMenuType.Hidden }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
|
||||||
return {
|
|
||||||
showItem: (feedId: string, item: RSSItem) =>
|
|
||||||
dispatch(showItem(feedId, item)),
|
|
||||||
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
|
||||||
markUnread: (item: RSSItem) => dispatch(markUnread(item)),
|
|
||||||
toggleStarred: (item: RSSItem) => dispatch(toggleStarred(item)),
|
|
||||||
toggleHidden: (item: RSSItem) => {
|
|
||||||
if (!item.hasRead) {
|
|
||||||
dispatch(markRead(item))
|
|
||||||
item.hasRead = true // get around chaining error
|
|
||||||
}
|
|
||||||
dispatch(toggleHidden(item))
|
|
||||||
},
|
|
||||||
switchView: (viewType: ViewType) => {
|
|
||||||
window.settings.setDefaultView(viewType)
|
|
||||||
dispatch(switchView(viewType))
|
|
||||||
},
|
|
||||||
setViewConfigs: (configs: ViewConfigs) =>
|
|
||||||
dispatch(setViewConfigs(configs)),
|
|
||||||
switchFilter: (filter: FilterType) => dispatch(switchFilter(filter)),
|
|
||||||
toggleFilter: (filter: FilterType) => dispatch(toggleFilter(filter)),
|
|
||||||
markAllRead: (sids?: number[], date?: Date, before?: boolean) => {
|
|
||||||
dispatch(markAllRead(sids, date, before))
|
|
||||||
},
|
|
||||||
fetchItems: (sids: number[]) => dispatch(fetchItems(false, sids)),
|
|
||||||
settings: (sids: number[]) => dispatch(toggleSettings(true, sids)),
|
|
||||||
close: () => dispatch(closeContextMenu()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const connector = connect(mapStateToProps, mapDispatchToProps)
|
|
||||||
export type ContextReduxProps = typeof connector
|
|
||||||
export const ContextMenuContainer = connector(ContextMenu)
|
|
@ -1,29 +1,21 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as ReactDOM from "react-dom"
|
import * as ReactDOM from "react-dom"
|
||||||
import { Provider } from "react-redux"
|
import { Provider } from "react-redux"
|
||||||
import { createStore, applyMiddleware } from "redux"
|
|
||||||
import thunkMiddleware from "redux-thunk"
|
|
||||||
import { initializeIcons } from "@fluentui/react/lib/Icons"
|
import { initializeIcons } from "@fluentui/react/lib/Icons"
|
||||||
import { rootReducer, RootState } from "./scripts/reducer"
|
|
||||||
import Root from "./components/root"
|
import Root from "./components/root"
|
||||||
import { AppDispatch } from "./scripts/utils"
|
|
||||||
import { applyThemeSettings } from "./scripts/settings"
|
import { applyThemeSettings } from "./scripts/settings"
|
||||||
import { initApp, openTextMenu } from "./scripts/models/app"
|
import { initApp, openTextMenu } from "./scripts/models/app"
|
||||||
|
import { rootStore } from "./scripts/reducer"
|
||||||
|
|
||||||
window.settings.setProxy()
|
window.settings.setProxy()
|
||||||
|
|
||||||
applyThemeSettings()
|
applyThemeSettings()
|
||||||
initializeIcons("icons/")
|
initializeIcons("icons/")
|
||||||
|
|
||||||
const store = createStore(
|
rootStore.dispatch(initApp())
|
||||||
rootReducer,
|
|
||||||
applyMiddleware<AppDispatch, RootState>(thunkMiddleware)
|
|
||||||
)
|
|
||||||
|
|
||||||
store.dispatch(initApp())
|
|
||||||
|
|
||||||
window.utils.addMainContextListener((pos, text) => {
|
window.utils.addMainContextListener((pos, text) => {
|
||||||
store.dispatch(openTextMenu(pos, text))
|
rootStore.dispatch(openTextMenu(pos, text))
|
||||||
})
|
})
|
||||||
|
|
||||||
window.fontList = [""]
|
window.fontList = [""]
|
||||||
@ -32,7 +24,7 @@ window.utils.initFontList().then(fonts => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={rootStore}>
|
||||||
<Root />
|
<Root />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
document.getElementById("app")
|
document.getElementById("app")
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { combineReducers } from "redux"
|
import { applyMiddleware, combineReducers, createStore } from "redux"
|
||||||
|
import thunkMiddleware from "redux-thunk"
|
||||||
|
|
||||||
import { sourceReducer } from "./models/source"
|
import { sourceReducer } from "./models/source"
|
||||||
import { itemReducer } from "./models/item"
|
import { itemReducer } from "./models/item"
|
||||||
@ -7,6 +8,13 @@ import { appReducer } from "./models/app"
|
|||||||
import { groupReducer } from "./models/group"
|
import { groupReducer } from "./models/group"
|
||||||
import { pageReducer } from "./models/page"
|
import { pageReducer } from "./models/page"
|
||||||
import { serviceReducer } from "./models/service"
|
import { serviceReducer } from "./models/service"
|
||||||
|
import { AppDispatch } from "./utils"
|
||||||
|
import {
|
||||||
|
TypedUseSelectorHook,
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
useStore,
|
||||||
|
} from "react-redux"
|
||||||
|
|
||||||
export const rootReducer = combineReducers({
|
export const rootReducer = combineReducers({
|
||||||
sources: sourceReducer,
|
sources: sourceReducer,
|
||||||
@ -18,4 +26,14 @@ export const rootReducer = combineReducers({
|
|||||||
app: appReducer,
|
app: appReducer,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const rootStore = createStore(
|
||||||
|
rootReducer,
|
||||||
|
applyMiddleware<AppDispatch, RootState>(thunkMiddleware)
|
||||||
|
)
|
||||||
|
|
||||||
|
export type AppStore = typeof rootStore
|
||||||
export type RootState = ReturnType<typeof rootReducer>
|
export type RootState = ReturnType<typeof rootReducer>
|
||||||
|
|
||||||
|
export const useAppDispatch: () => AppDispatch = useDispatch
|
||||||
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
||||||
|
export const useAppStore: () => AppStore = useStore
|
||||||
|
Loading…
x
Reference in New Issue
Block a user