toggle snippet / cover in list view

This commit is contained in:
刘浩远 2020-08-24 13:32:34 +08:00
parent 478a12c036
commit f2d5ca0171
17 changed files with 135 additions and 22 deletions

16
dist/styles/cards.css vendored
View File

@ -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;

View File

@ -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

View File

@ -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
},

View File

@ -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

View File

@ -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>
)

View File

@ -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? [

View File

@ -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

View File

@ -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,

View File

@ -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) => {

View File

@ -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,
})
)
}

View File

@ -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
}
})

View File

@ -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
}

View File

@ -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",

View File

@ -89,7 +89,9 @@
"saveImageAs": "将图像另存为",
"copyImage": "复制图像",
"copyImageURL": "复制图像链接",
"caseSensitive": "区分大小写"
"caseSensitive": "区分大小写",
"showCover": "显示封面",
"showSnippet": "显示摘要"
},
"searchEngine": {
"name": "搜索引擎",

View File

@ -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,

View File

@ -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) + "…"