diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index fd9ac50..6c74aaf 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -14,9 +14,9 @@ jobs: - name: Build and package the app run: | - sudo npm install --unsafe-perm=true --allow-root + npm install npm run build - sudo npm run package-linux + npm run package-linux - name: Get app version id: package-version diff --git a/electron-builder-mas.yml b/electron-builder-mas.yml index 0390607..758822a 100644 --- a/electron-builder-mas.yml +++ b/electron-builder-mas.yml @@ -1,5 +1,5 @@ appId: DevHYLiu.FluentReader -buildVersion: 26 +buildVersion: 27 productName: Fluent Reader copyright: Copyright © 2020 Haoyuan Liu files: diff --git a/package-lock.json b/package-lock.json index a8c2892..4ff4276 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "fluent-reader", - "version": "1.1.1", + "version": "1.1.2", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index c5c9394..f16740c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fluent-reader", - "version": "1.1.1", + "version": "1.1.2", "description": "Modern desktop RSS reader", "main": "./dist/electron.js", "scripts": { diff --git a/src/components/menu.tsx b/src/components/menu.tsx index 9171102..a95532a 100644 --- a/src/components/menu.tsx +++ b/src/components/menu.tsx @@ -49,6 +49,7 @@ export class Menu extends React.Component { intl.get("allArticles") + this.countOverflow( Object.values(this.props.sources) + .filter(s => !s.hidden) .map(s => s.unreadCount) .reduce((a, b) => a + b, 0) ), diff --git a/src/components/settings/sources.tsx b/src/components/settings/sources.tsx index 7781224..2440a35 100644 --- a/src/components/settings/sources.tsx +++ b/src/components/settings/sources.tsx @@ -16,6 +16,7 @@ import { Dropdown, MessageBar, MessageBarType, + Toggle, } from "@fluentui/react" import { SourceState, @@ -42,6 +43,7 @@ type SourcesTabProps = { deleteSources: (sources: RSSSource[]) => void importOPML: () => void exportOPML: () => void + toggleSourceHidden: (source: RSSSource) => void } type SourcesTabState = { @@ -217,6 +219,16 @@ class SourcesTab extends React.Component { }) } + onToggleHidden = () => { + this.props.toggleSourceHidden(this.state.selectedSource) + this.setState({ + selectedSource: { + ...this.state.selectedSource, + hidden: !this.state.selectedSource.hidden, + } as RSSSource, + }) + } + render = () => (
{this.props.serviceOn && ( @@ -409,6 +421,17 @@ class SourcesTab extends React.Component { )} onChange={this.onOpenTargetChange} /> + + + + + + + + {!this.state.selectedSource.serviceRef && ( diff --git a/src/containers/settings/sources-container.tsx b/src/containers/settings/sources-container.tsx index e91673c..80947d0 100644 --- a/src/containers/settings/sources-container.tsx +++ b/src/containers/settings/sources-container.tsx @@ -10,6 +10,7 @@ import { deleteSource, SourceOpenTarget, deleteSources, + toggleSourceHidden, } from "../../scripts/models/source" import { importOPML, exportOPML } from "../../scripts/models/group" import { AppDispatch, validateFavicon } from "../../scripts/utils" @@ -67,6 +68,8 @@ const mapDispatchToProps = (dispatch: AppDispatch) => { dispatch(deleteSources(sources)), importOPML: () => dispatch(importOPML()), exportOPML: () => dispatch(exportOPML()), + toggleSourceHidden: (source: RSSSource) => + dispatch(toggleSourceHidden(source)), } } diff --git a/src/scripts/db.ts b/src/scripts/db.ts index b0c032b..f6a0518 100644 --- a/src/scripts/db.ts +++ b/src/scripts/db.ts @@ -4,7 +4,7 @@ import lf from "lovefield" import { RSSSource } from "./models/source" import { RSSItem } from "./models/item" -const sdbSchema = lf.schema.create("sourcesDB", 2) +const sdbSchema = lf.schema.create("sourcesDB", 3) sdbSchema .createTable("sources") .addColumn("sid", lf.Type.INTEGER) @@ -18,6 +18,7 @@ sdbSchema .addColumn("fetchFrequency", lf.Type.NUMBER) .addColumn("rules", lf.Type.OBJECT) .addColumn("textDir", lf.Type.NUMBER) + .addColumn("hidden", lf.Type.BOOLEAN) .addNullable(["iconurl", "serviceRef", "rules"]) .addIndex("idxURL", ["url"], true) @@ -54,6 +55,9 @@ async function onUpgradeSourceDB(rawDb: lf.raw.BackStore) { if (version < 2) { await rawDb.addTableColumn("sources", "textDir", 0) } + if (version < 3) { + await rawDb.addTableColumn("sources", "hidden", false) + } } export async function init() { @@ -99,6 +103,7 @@ async function migrateNeDB() { delete doc._id if (!doc.fetchFrequency) doc.fetchFrequency = 0 doc.textDir = 0 + doc.hidden = false return sources.createRow(doc) }) const iRows = itemDocs.map(doc => { diff --git a/src/scripts/i18n/en-US.json b/src/scripts/i18n/en-US.json index 76f0442..fad7eac 100644 --- a/src/scripts/i18n/en-US.json +++ b/src/scripts/i18n/en-US.json @@ -149,7 +149,8 @@ "badUrl": "Invalid URL", "deleteWarning": "The source and all saved articles will be removed.", "selected": "Selected source", - "selectedMulti": "Selected multiple sources" + "selectedMulti": "Selected multiple sources", + "hidden": "Hide in \"all articles\"" }, "groups": { "exist": "This group already exists.", diff --git a/src/scripts/i18n/zh-CN.json b/src/scripts/i18n/zh-CN.json index 81c16cb..bbe9a25 100644 --- a/src/scripts/i18n/zh-CN.json +++ b/src/scripts/i18n/zh-CN.json @@ -147,7 +147,8 @@ "badUrl": "请正确输入URL", "deleteWarning": "这将移除订阅源与所有已保存的文章", "selected": "选中订阅源", - "selectedMulti": "选中多个订阅源" + "selectedMulti": "选中多个订阅源", + "hidden": "从“全部文章”中隐藏" }, "groups": { "exist": "该分组已存在", diff --git a/src/scripts/i18n/zh-TW.json b/src/scripts/i18n/zh-TW.json index 10adf00..d59d5fc 100644 --- a/src/scripts/i18n/zh-TW.json +++ b/src/scripts/i18n/zh-TW.json @@ -147,7 +147,8 @@ "badUrl": "請正確輸入URL", "deleteWarning": "這將移除訂閱源與所有已儲存的文章", "selected": "選中訂閱源", - "selectedMulti": "選中多個訂閱源" + "selectedMulti": "選中多個訂閱源", + "hidden": "從“全部文章”中隱藏" }, "groups": { "exist": "該分組已存在", diff --git a/src/scripts/models/feed.ts b/src/scripts/models/feed.ts index baec38e..23ce335 100644 --- a/src/scripts/models/feed.ts +++ b/src/scripts/models/feed.ts @@ -5,6 +5,8 @@ import { INIT_SOURCES, ADD_SOURCE, DELETE_SOURCE, + UNHIDE_SOURCE, + HIDE_SOURCE, } from "./source" import { ItemActionTypes, @@ -316,13 +318,16 @@ export function feedReducer( ...state, [ALL]: new RSSFeed( ALL, - Object.values(action.sources).map(s => s.sid) + Object.values(action.sources) + .filter(s => !s.hidden) + .map(s => s.sid) ), } default: return state } case ADD_SOURCE: + case UNHIDE_SOURCE: switch (action.status) { case ActionStatus.Success: return { @@ -336,7 +341,8 @@ export function feedReducer( default: return state } - case DELETE_SOURCE: { + case DELETE_SOURCE: + case HIDE_SOURCE: { let nextState = {} for (let [id, feed] of Object.entries(state)) { nextState[id] = new RSSFeed( diff --git a/src/scripts/models/source.ts b/src/scripts/models/source.ts index 66ed585..c3ba420 100644 --- a/src/scripts/models/source.ts +++ b/src/scripts/models/source.ts @@ -41,6 +41,7 @@ export class RSSSource { fetchFrequency: number // in minutes rules?: SourceRule[] textDir: SourceTextDirection + hidden: boolean constructor(url: string, name: string = null) { this.url = url @@ -49,6 +50,7 @@ export class RSSSource { this.lastFetched = new Date() this.fetchFrequency = 0 this.textDir = SourceTextDirection.LTR + this.hidden = false } static async fetchMetaData(source: RSSSource) { @@ -120,6 +122,8 @@ export const ADD_SOURCE = "ADD_SOURCE" export const UPDATE_SOURCE = "UPDATE_SOURCE" export const UPDATE_UNREAD_COUNTS = "UPDATE_UNREAD_COUNTS" export const DELETE_SOURCE = "DELETE_SOURCE" +export const HIDE_SOURCE = "HIDE_SOURCE" +export const UNHIDE_SOURCE = "UNHIDE_SOURCE" interface InitSourcesAction { type: typeof INIT_SOURCES @@ -151,12 +155,19 @@ interface DeleteSourceAction { source: RSSSource } +interface ToggleSourceHiddenAction { + type: typeof HIDE_SOURCE | typeof UNHIDE_SOURCE + status: ActionStatus + source: RSSSource +} + export type SourceActionTypes = | InitSourcesAction | AddSourceAction | UpdateSourceAction | UpdateUnreadCountsAction | DeleteSourceAction + | ToggleSourceHiddenAction export function initSourcesRequest(): SourceActionTypes { return { @@ -382,6 +393,19 @@ export function deleteSources(sources: RSSSource[]): AppThunk> { } } +export function toggleSourceHidden(source: RSSSource): AppThunk> { + return async (dispatch, getState) => { + const sourceCopy: RSSSource = { ...getState().sources[source.sid] } + sourceCopy.hidden = !sourceCopy.hidden + dispatch({ + type: sourceCopy.hidden ? HIDE_SOURCE : UNHIDE_SOURCE, + status: ActionStatus.Success, + source: sourceCopy, + }) + await dispatch(updateSource(sourceCopy)) + } +} + export function updateFavicon( sids?: number[], force = false diff --git a/src/scripts/settings.ts b/src/scripts/settings.ts index a7e7fa6..c6302e3 100644 --- a/src/scripts/settings.ts +++ b/src/scripts/settings.ts @@ -146,6 +146,7 @@ export async function importAll() { const sRows = configs.lovefield.sources.map(s => { s.lastFetched = new Date(s.lastFetched) if (!s.textDir) s.textDir = SourceTextDirection.LTR + if (!s.hidden) s.hidden = false return db.sources.createRow(s) }) const iRows = configs.lovefield.items.map(i => {