mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-03-23 06:50:05 +01:00
toggle snippet / cover in list view
This commit is contained in:
parent
478a12c036
commit
f2d5ca0171
16
dist/styles/cards.css
vendored
16
dist/styles/cards.css
vendored
@ -195,22 +195,34 @@
|
||||
}
|
||||
.list-card .data {
|
||||
flex-grow: 1;
|
||||
margin: 8px 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.list-card .info {
|
||||
margin: 8px 10px;
|
||||
margin: 0 0 8px;
|
||||
height: 16px;
|
||||
}
|
||||
.list-card h3.title {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
font-weight: 600;
|
||||
margin: 8px 10px;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.list-card p.snippet {
|
||||
color: var(--neutralSecondary);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
margin: 4px 0 0;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.magazine-card {
|
||||
width: 700px;
|
||||
|
2
dist/styles/main.css
vendored
2
dist/styles/main.css
vendored
@ -239,7 +239,7 @@ body.darwin .list-main .article-search {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1441px) {
|
||||
@media (min-width: 1440px) {
|
||||
#root > nav.menu-on {
|
||||
padding-left: 296px;
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 86 KiB |
@ -1,4 +1,4 @@
|
||||
import { SourceGroup, ViewType, ThemeSettings, SearchEngines, ServiceConfigs } from "../schema-types"
|
||||
import { SourceGroup, ViewType, ThemeSettings, SearchEngines, ServiceConfigs, ViewConfigs } from "../schema-types"
|
||||
import { ipcRenderer } from "electron"
|
||||
|
||||
const settingsBridge = {
|
||||
@ -96,6 +96,13 @@ const settingsBridge = {
|
||||
ipcRenderer.invoke("set-filter-type", filterType)
|
||||
},
|
||||
|
||||
getViewConfigs: (view: ViewType): ViewConfigs => {
|
||||
return ipcRenderer.sendSync("get-view-configs", view)
|
||||
},
|
||||
setViewConfigs: (view: ViewType, configs: ViewConfigs) => {
|
||||
ipcRenderer.invoke("set-view-configs", view, configs)
|
||||
},
|
||||
|
||||
getAll: () => {
|
||||
return ipcRenderer.sendSync("get-all-settings") as Object
|
||||
},
|
||||
|
@ -3,6 +3,7 @@ import { RSSSource, SourceOpenTarget } from "../../scripts/models/source"
|
||||
import { RSSItem } from "../../scripts/models/item"
|
||||
import { platformCtrl } from "../../scripts/utils"
|
||||
import { FeedFilter } from "../../scripts/models/feed"
|
||||
import { ViewConfigs } from "../../schema-types"
|
||||
|
||||
export namespace Card {
|
||||
export type Props = {
|
||||
@ -10,6 +11,7 @@ export namespace Card {
|
||||
item: RSSItem
|
||||
source: RSSSource
|
||||
filter: FeedFilter
|
||||
viewConfigs?: ViewConfigs
|
||||
shortcuts: (item: RSSItem, e: KeyboardEvent) => void
|
||||
markRead: (item: RSSItem) => void
|
||||
contextMenu: (feedId: string, item: RSSItem, e) => void
|
||||
|
@ -2,6 +2,7 @@ import * as React from "react"
|
||||
import { Card } from "./card"
|
||||
import CardInfo from "./info"
|
||||
import Highlights from "./highlights"
|
||||
import { ViewConfigs } from "../../schema-types"
|
||||
|
||||
const className = (props: Card.Props) => {
|
||||
let cn = ["card", "list-card"]
|
||||
@ -15,12 +16,15 @@ const ListCard: React.FunctionComponent<Card.Props> = (props) => (
|
||||
{...Card.bindEventsToProps(props)}
|
||||
data-iid={props.item._id}
|
||||
data-is-focusable>
|
||||
{props.item.thumb ? (
|
||||
{props.item.thumb && (props.viewConfigs & ViewConfigs.ShowCover) ? (
|
||||
<div className="head"><img src={props.item.thumb} /></div>
|
||||
) : null}
|
||||
<div className="data">
|
||||
<CardInfo source={props.source} item={props.item} />
|
||||
<h3 className="title"><Highlights text={props.item.title} filter={props.filter} title /></h3>
|
||||
{Boolean(props.viewConfigs & ViewConfigs.ShowSnippet) && (
|
||||
<p className="snippet"><Highlights text={props.item.snippet} filter={props.filter} /></p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -6,7 +6,7 @@ import { ContextualMenu, IContextualMenuItem, ContextualMenuItemType, Directiona
|
||||
import { ContextMenuType } from "../scripts/models/app"
|
||||
import { RSSItem } from "../scripts/models/item"
|
||||
import { ContextReduxProps } from "../containers/context-menu-container"
|
||||
import { ViewType, ImageCallbackTypes } from "../schema-types"
|
||||
import { ViewType, ImageCallbackTypes, ViewConfigs } from "../schema-types"
|
||||
import { FilterType } from "../scripts/models/feed"
|
||||
|
||||
export type ContextMenuProps = ContextReduxProps & {
|
||||
@ -18,6 +18,7 @@ export type ContextMenuProps = ContextReduxProps & {
|
||||
text?: string
|
||||
url?: string
|
||||
viewType?: ViewType
|
||||
viewConfigs?: ViewConfigs
|
||||
filter?: FilterType
|
||||
sids?: number[]
|
||||
showItem: (feedId: string, item: RSSItem) => void
|
||||
@ -26,6 +27,7 @@ export type ContextMenuProps = ContextReduxProps & {
|
||||
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
|
||||
@ -142,7 +144,35 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
key: "copyURL",
|
||||
text: intl.get("context.copyURL"),
|
||||
onClick: () => { window.utils.writeClipboard(this.props.item.link) }
|
||||
}
|
||||
},
|
||||
...(this.props.viewConfigs !== undefined ? [
|
||||
{
|
||||
key: "divider_2",
|
||||
itemType: ContextualMenuItemType.Divider,
|
||||
},
|
||||
{
|
||||
key: "view",
|
||||
text: intl.get("context.view"),
|
||||
subMenuProps: {
|
||||
items: [
|
||||
{
|
||||
key: "showCover",
|
||||
text: intl.get("context.showCover"),
|
||||
canCheck: true,
|
||||
checked: Boolean(this.props.viewConfigs & ViewConfigs.ShowCover),
|
||||
onClick: () => this.props.setViewConfigs(this.props.viewConfigs ^ ViewConfigs.ShowCover)
|
||||
},
|
||||
{
|
||||
key: "showSnippet",
|
||||
text: intl.get("context.showSnippet"),
|
||||
canCheck: true,
|
||||
checked: Boolean(this.props.viewConfigs & ViewConfigs.ShowSnippet),
|
||||
onClick: () => this.props.setViewConfigs(this.props.viewConfigs ^ ViewConfigs.ShowSnippet)
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
] : [])
|
||||
]
|
||||
case ContextMenuType.Text: {
|
||||
const items: IContextualMenuItem[] = this.props.text? [
|
||||
|
@ -2,13 +2,14 @@ import * as React from "react"
|
||||
import { RSSItem } from "../../scripts/models/item"
|
||||
import { FeedReduxProps } from "../../containers/feed-container"
|
||||
import { RSSFeed, FeedFilter } from "../../scripts/models/feed"
|
||||
import { ViewType } from "../../schema-types"
|
||||
import { ViewType, ViewConfigs } from "../../schema-types"
|
||||
import CardsFeed from "./cards-feed"
|
||||
import ListFeed from "./list-feed"
|
||||
|
||||
export type FeedProps = FeedReduxProps & {
|
||||
feed: RSSFeed
|
||||
viewType: ViewType
|
||||
viewConfigs?: ViewConfigs
|
||||
items: RSSItem[]
|
||||
sourceMap: Object
|
||||
filter: FeedFilter
|
||||
|
@ -17,6 +17,7 @@ class ListFeed extends React.Component<FeedProps> {
|
||||
item: item,
|
||||
source: this.props.sourceMap[item.source],
|
||||
filter: this.props.filter,
|
||||
viewConfigs: this.props.viewConfigs,
|
||||
shortcuts: this.props.shortcuts,
|
||||
markRead: this.props.markRead,
|
||||
contextMenu: this.props.contextMenu,
|
||||
|
@ -4,21 +4,23 @@ 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 } from "../scripts/models/item"
|
||||
import { showItem, switchView, switchFilter, toggleFilter } from "../scripts/models/page"
|
||||
import { ViewType } from "../schema-types"
|
||||
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],
|
||||
(context, viewType, filter) => {
|
||||
[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]
|
||||
}
|
||||
@ -65,6 +67,7 @@ const mapDispatchToProps = dispatch => {
|
||||
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) => {
|
||||
|
@ -18,16 +18,18 @@ const getItems = (state: RootState) => state.items
|
||||
const getFeed = (state: RootState, props: FeedContainerProps) => state.feeds[props.feedId]
|
||||
const getFilter = (state: RootState) => state.page.filter
|
||||
const getView = (_, props: FeedContainerProps) => props.viewType
|
||||
const getViewConfigs = (state: RootState) => state.page.viewConfigs
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
return createSelector(
|
||||
[getSources, getItems, getFeed, getView, getFilter],
|
||||
(sources, items, feed, viewType, filter) => ({
|
||||
[getSources, getItems, getFeed, getView, getFilter, getViewConfigs],
|
||||
(sources, items, feed, viewType, filter, viewConfigs) => ({
|
||||
feed: feed,
|
||||
items: feed.iids.map(iid => items[iid]),
|
||||
sourceMap: sources,
|
||||
filter: filter,
|
||||
viewType: viewType
|
||||
viewType: viewType,
|
||||
viewConfigs: viewConfigs,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Store = require("electron-store")
|
||||
import { SchemaTypes, SourceGroup, ViewType, ThemeSettings, SearchEngines,
|
||||
SyncService, ServiceConfigs } from "../schema-types"
|
||||
SyncService, ServiceConfigs, ViewConfigs } from "../schema-types"
|
||||
import { ipcMain, session, nativeTheme, app } from "electron"
|
||||
import { WindowManager } from "./window"
|
||||
|
||||
@ -153,3 +153,22 @@ ipcMain.on("get-filter-type", (event) => {
|
||||
ipcMain.handle("set-filter-type", (_, filterType: number) => {
|
||||
store.set(FILTER_TYPE_STORE_KEY, filterType)
|
||||
})
|
||||
|
||||
const LIST_CONFIGS_STORE_KEY = "listViewConfigs"
|
||||
ipcMain.on("get-view-configs", (event, view: ViewType) => {
|
||||
switch (view) {
|
||||
case ViewType.List:
|
||||
event.returnValue = store.get(LIST_CONFIGS_STORE_KEY, ViewConfigs.ShowCover)
|
||||
break
|
||||
default:
|
||||
event.returnValue = undefined
|
||||
break
|
||||
}
|
||||
})
|
||||
ipcMain.handle("set-view-configs", (_, view: ViewType, configs: ViewConfigs) => {
|
||||
switch (view) {
|
||||
case ViewType.List:
|
||||
store.set(LIST_CONFIGS_STORE_KEY, configs)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
@ -22,6 +22,11 @@ export const enum ViewType {
|
||||
Cards, List, Magazine, Compact, Customized
|
||||
}
|
||||
|
||||
export const enum ViewConfigs {
|
||||
ShowCover = 1 << 0,
|
||||
ShowSnippet = 1 << 1,
|
||||
}
|
||||
|
||||
export const enum ThemeSettings {
|
||||
Default = "system",
|
||||
Light = "light",
|
||||
@ -70,4 +75,5 @@ export type SchemaTypes = {
|
||||
searchEngine: SearchEngines
|
||||
serviceConfigs: ServiceConfigs
|
||||
filterType: number
|
||||
listViewConfigs: ViewConfigs
|
||||
}
|
||||
|
@ -89,7 +89,9 @@
|
||||
"saveImageAs": "Save image as …",
|
||||
"copyImage": "Copy image",
|
||||
"copyImageURL": "Copy image link",
|
||||
"caseSensitive": "Case sensitive"
|
||||
"caseSensitive": "Case sensitive",
|
||||
"showCover": "Show cover",
|
||||
"showSnippet": "Show snippet"
|
||||
},
|
||||
"searchEngine": {
|
||||
"name": "Search engine",
|
||||
|
@ -89,7 +89,9 @@
|
||||
"saveImageAs": "将图像另存为",
|
||||
"copyImage": "复制图像",
|
||||
"copyImageURL": "复制图像链接",
|
||||
"caseSensitive": "区分大小写"
|
||||
"caseSensitive": "区分大小写",
|
||||
"showCover": "显示封面",
|
||||
"showSnippet": "显示摘要"
|
||||
},
|
||||
"searchEngine": {
|
||||
"name": "搜索引擎",
|
||||
|
@ -3,10 +3,11 @@ import { getWindowBreakpoint, AppThunk, ActionStatus } from "../utils"
|
||||
import { RSSItem, markRead } from "./item"
|
||||
import { SourceActionTypes, DELETE_SOURCE } from "./source"
|
||||
import { toggleMenu } from "./app"
|
||||
import { ViewType } from "../../schema-types"
|
||||
import { ViewType, ViewConfigs } from "../../schema-types"
|
||||
|
||||
export const SELECT_PAGE = "SELECT_PAGE"
|
||||
export const SWITCH_VIEW = "SWITCH_VIEW"
|
||||
export const SET_VIEW_CONFIGS = "SET_VIEW_CONFIGS"
|
||||
export const SHOW_ITEM = "SHOW_ITEM"
|
||||
export const SHOW_OFFSET_ITEM = "SHOW_OFFSET_ITEM"
|
||||
export const DISMISS_ITEM = "DISMISS_ITEM"
|
||||
@ -33,6 +34,11 @@ interface SwitchViewAction {
|
||||
viewType: ViewType
|
||||
}
|
||||
|
||||
interface SetViewConfigsAction {
|
||||
type: typeof SET_VIEW_CONFIGS
|
||||
configs: ViewConfigs
|
||||
}
|
||||
|
||||
interface ShowItemAction {
|
||||
type: typeof SHOW_ITEM
|
||||
feedId: string
|
||||
@ -48,7 +54,7 @@ interface DismissItemAction { type: typeof DISMISS_ITEM }
|
||||
interface ToggleSearchAction { type: typeof TOGGLE_SEARCH }
|
||||
|
||||
export type PageActionTypes = SelectPageAction | SwitchViewAction | ShowItemAction
|
||||
| DismissItemAction | ApplyFilterAction | ToggleSearchAction
|
||||
| DismissItemAction | ApplyFilterAction | ToggleSearchAction | SetViewConfigsAction
|
||||
|
||||
export function selectAllArticles(init = false): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
@ -86,6 +92,16 @@ export function switchView(viewType: ViewType): PageActionTypes {
|
||||
}
|
||||
}
|
||||
|
||||
export function setViewConfigs(configs: ViewConfigs): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
window.settings.setViewConfigs(getState().page.viewType, configs)
|
||||
dispatch({
|
||||
type: "SET_VIEW_CONFIGS",
|
||||
configs: configs
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function showItem(feedId: string, item: RSSItem): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState()
|
||||
@ -218,6 +234,7 @@ export function performSearch(query: string): AppThunk {
|
||||
|
||||
export class PageState {
|
||||
viewType = window.settings.getDefaultView()
|
||||
viewConfigs = window.settings.getViewConfigs(window.settings.getDefaultView())
|
||||
filter = new FeedFilter()
|
||||
feedId = ALL
|
||||
itemId = null as string
|
||||
@ -247,7 +264,12 @@ export function pageReducer(
|
||||
case SWITCH_VIEW: return {
|
||||
...state,
|
||||
viewType: action.viewType,
|
||||
itemId: null
|
||||
viewConfigs: window.settings.getViewConfigs(action.viewType),
|
||||
itemId: null
|
||||
}
|
||||
case SET_VIEW_CONFIGS: return {
|
||||
...state,
|
||||
viewConfigs: action.configs
|
||||
}
|
||||
case APPLY_FILTER: return {
|
||||
...state,
|
||||
|
@ -124,7 +124,7 @@ export function htmlDecode(input: string) {
|
||||
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)
|
||||
|
||||
export const getWindowBreakpoint = () => window.outerWidth >= 1441
|
||||
export const getWindowBreakpoint = () => window.outerWidth >= 1440
|
||||
|
||||
export const cutText = (s: string, length: number) => {
|
||||
return (s.length <= length) ? s : s.slice(0, length) + "…"
|
||||
|
Loading…
x
Reference in New Issue
Block a user