diff --git a/dist/styles/cards.css b/dist/styles/cards.css index 9a4d3b6..f921442 100644 --- a/dist/styles/cards.css +++ b/dist/styles/cards.css @@ -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; diff --git a/dist/styles/main.css b/dist/styles/main.css index b2758b6..0eb0a97 100644 --- a/dist/styles/main.css +++ b/dist/styles/main.css @@ -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; } diff --git a/docs/imgs/search.png b/docs/imgs/search.png index 88ca6e6..2845983 100644 Binary files a/docs/imgs/search.png and b/docs/imgs/search.png differ diff --git a/src/bridges/settings.ts b/src/bridges/settings.ts index b45cd16..2527837 100644 --- a/src/bridges/settings.ts +++ b/src/bridges/settings.ts @@ -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 }, diff --git a/src/components/cards/card.tsx b/src/components/cards/card.tsx index db6566a..2d31d4c 100644 --- a/src/components/cards/card.tsx +++ b/src/components/cards/card.tsx @@ -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 diff --git a/src/components/cards/list-card.tsx b/src/components/cards/list-card.tsx index 35b601b..c793e66 100644 --- a/src/components/cards/list-card.tsx +++ b/src/components/cards/list-card.tsx @@ -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 = (props) => ( {...Card.bindEventsToProps(props)} data-iid={props.item._id} data-is-focusable> - {props.item.thumb ? ( + {props.item.thumb && (props.viewConfigs & ViewConfigs.ShowCover) ? (
) : null}

+ {Boolean(props.viewConfigs & ViewConfigs.ShowSnippet) && ( +

+ )}
) diff --git a/src/components/context-menu.tsx b/src/components/context-menu.tsx index 5db7eea..b3217e9 100644 --- a/src/components/context-menu.tsx +++ b/src/components/context-menu.tsx @@ -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 { 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? [ diff --git a/src/components/feeds/feed.tsx b/src/components/feeds/feed.tsx index 8f337f3..0622066 100644 --- a/src/components/feeds/feed.tsx +++ b/src/components/feeds/feed.tsx @@ -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 diff --git a/src/components/feeds/list-feed.tsx b/src/components/feeds/list-feed.tsx index 063f1a7..f55b383 100644 --- a/src/components/feeds/list-feed.tsx +++ b/src/components/feeds/list-feed.tsx @@ -17,6 +17,7 @@ class ListFeed extends React.Component { 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, diff --git a/src/containers/context-menu-container.tsx b/src/containers/context-menu-container.tsx index 1904fcd..10e301d 100644 --- a/src/containers/context-menu-container.tsx +++ b/src/containers/context-menu-container.tsx @@ -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) => { diff --git a/src/containers/feed-container.tsx b/src/containers/feed-container.tsx index 55476bb..21173e9 100644 --- a/src/containers/feed-container.tsx +++ b/src/containers/feed-container.tsx @@ -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, }) ) } diff --git a/src/main/settings.ts b/src/main/settings.ts index ea87c8b..53f0b50 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -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 + } +}) diff --git a/src/schema-types.ts b/src/schema-types.ts index 76a228d..2036df0 100644 --- a/src/schema-types.ts +++ b/src/schema-types.ts @@ -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 } diff --git a/src/scripts/i18n/en-US.json b/src/scripts/i18n/en-US.json index 2d0aef1..ddd28af 100644 --- a/src/scripts/i18n/en-US.json +++ b/src/scripts/i18n/en-US.json @@ -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", diff --git a/src/scripts/i18n/zh-CN.json b/src/scripts/i18n/zh-CN.json index fae29d3..1570d1f 100644 --- a/src/scripts/i18n/zh-CN.json +++ b/src/scripts/i18n/zh-CN.json @@ -89,7 +89,9 @@ "saveImageAs": "将图像另存为", "copyImage": "复制图像", "copyImageURL": "复制图像链接", - "caseSensitive": "区分大小写" + "caseSensitive": "区分大小写", + "showCover": "显示封面", + "showSnippet": "显示摘要" }, "searchEngine": { "name": "搜索引擎", diff --git a/src/scripts/models/page.ts b/src/scripts/models/page.ts index 4e24930..1c8781b 100644 --- a/src/scripts/models/page.ts +++ b/src/scripts/models/page.ts @@ -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, diff --git a/src/scripts/utils.ts b/src/scripts/utils.ts index 21af1b9..854b94a 100644 --- a/src/scripts/utils.ts +++ b/src/scripts/utils.ts @@ -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) + "…"