import groups from service & fix freshrss

This commit is contained in:
刘浩远 2020-08-06 16:39:27 +08:00
parent 38646b227c
commit 8b27f9cb99
12 changed files with 65 additions and 30 deletions

1
.gitattributes vendored
View File

@ -1 +1,2 @@
dist/article/article.js text eol=lf
dist/article/mercury.web.js text eol=lf

View File

@ -96,6 +96,9 @@ div[role="tabpanel"] {
.settings .loading .ms-Spinner {
margin-top: 180px;
}
.settings .loading .ms-Spinner:focus {
outline: none;
}
.tab-body .ms-StackItem {
margin-right: 6px;
margin-bottom: 12px;

View File

@ -3,7 +3,7 @@ import intl from "react-intl-universal"
import { Icon } from "@fluentui/react/lib/Icon"
import { AnimationClassNames } from "@fluentui/react/lib/Styling"
import AboutTab from "./settings/about"
import { Pivot, PivotItem, Spinner } from "@fluentui/react"
import { Pivot, PivotItem, Spinner, FocusTrapZone } from "@fluentui/react"
import SourcesTabContainer from "../containers/settings/sources-container"
import GroupsTabContainer from "../containers/settings/groups-container"
import AppTabContainer from "../containers/settings/app-container"
@ -30,9 +30,11 @@ class Settings extends React.Component<SettingsProps> {
</a>
</div>
<div className={"settings " + AnimationClassNames.slideUpIn20}>
{this.props.blocked && <div className="loading">
<Spinner label={intl.get("settings.fetching")} />
</div>}
{this.props.blocked && (
<FocusTrapZone isClickableOutsideFocusTrap={true} className="loading">
<Spinner label={intl.get("settings.fetching")} tabIndex={0} />
</FocusTrapZone>
)}
<Pivot>
<PivotItem headerText={intl.get("settings.sources")} itemIcon="Source">
<SourcesTabContainer />

View File

@ -3,19 +3,20 @@ import intl from "react-intl-universal"
import { SourceGroup } from "../../schema-types"
import { SourceState, RSSSource } from "../../scripts/models/source"
import { IColumn, Selection, SelectionMode, DetailsList, Label, Stack,
TextField, PrimaryButton, DefaultButton, Dropdown, IDropdownOption, CommandBarButton, MarqueeSelection, IDragDropEvents, MessageBar, MessageBarType } from "@fluentui/react"
TextField, PrimaryButton, DefaultButton, Dropdown, IDropdownOption, CommandBarButton, MarqueeSelection, IDragDropEvents, MessageBar, MessageBarType, MessageBarButton } from "@fluentui/react"
import DangerButton from "../utils/danger-button"
type GroupsTabProps = {
sources: SourceState,
groups: SourceGroup[],
serviceOn: boolean,
createGroup: (name: string) => void,
updateGroup: (group: SourceGroup) => void,
addToGroup: (groupIndex: number, sid: number) => void,
deleteGroup: (groupIndex: number) => void,
removeFromGroup: (groupIndex: number, sids: number[]) => void,
sources: SourceState
groups: SourceGroup[]
serviceOn: boolean
createGroup: (name: string) => void
updateGroup: (group: SourceGroup) => void
addToGroup: (groupIndex: number, sid: number) => void
deleteGroup: (groupIndex: number) => void
removeFromGroup: (groupIndex: number, sids: number[]) => void
reorderGroups: (groups: SourceGroup[]) => void
importGroups: () => Promise<void>
}
type GroupsTabState = {
@ -264,9 +265,6 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
render = () => (
<div className="tab-body">
{this.props.serviceOn && (
<MessageBar messageBarType={MessageBarType.info}>{intl.get("service.groupsWarning")}</MessageBar>
)}
{this.state.manageGroup && this.state.selectedGroup &&
<>
<Stack horizontal horizontalAlign="space-between" style={{height: 40}}>
@ -295,6 +293,14 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
</>}
{(!this.state.manageGroup || !this.state.selectedGroup)
?<>
{this.props.serviceOn && (
<MessageBar
messageBarType={MessageBarType.info}
isMultiline={false}
actions={<MessageBarButton text={intl.get("service.importGroups")} onClick={this.props.importGroups} />}>
{intl.get("service.groupsWarning")}
</MessageBar>
)}
<form onSubmit={this.createGroup}>
<Label htmlFor="newGroupName">{intl.get("groups.create")}</Label>
<Stack horizontal>

View File

@ -98,9 +98,6 @@ class FeverConfigsTab extends React.Component<ServiceConfigsTabProps, FeverConfi
{!this.state.existing && (
<MessageBar messageBarType={MessageBarType.warning}>{intl.get("service.overwriteWarning")}</MessageBar>
)}
{!this.state.existing && this.state.importGroups && (
<MessageBar messageBarType={MessageBarType.info}>{intl.get("service.groupsWarning")}</MessageBar>
)}
<Stack horizontalAlign="center" style={{marginTop: 48}}>
<Icon iconName="Calories" style={{color: "var(--black)", fontSize: 32, userSelect: "none"}} />
<Label style={{margin: "8px 0 36px"}}>Fever API</Label>

View File

@ -5,6 +5,8 @@ import GroupsTab from "../../components/settings/groups"
import { createSourceGroup, updateSourceGroup, addSourceToGroup,
deleteSourceGroup, removeSourceFromGroup, reorderSourceGroups } from "../../scripts/models/group"
import { SourceGroup, SyncService } from "../../schema-types"
import { importGroups } from "../../scripts/models/service"
import { AppDispatch } from "../../scripts/utils"
const getSources = (state: RootState) => state.sources
const getGroups = (state: RootState) => state.groups
@ -20,13 +22,14 @@ const mapStateToProps = createSelector(
})
)
const mapDispatchToProps = dispatch => ({
const mapDispatchToProps = (dispatch: AppDispatch) => ({
createGroup: (name: string) => dispatch(createSourceGroup(name)),
updateGroup: (group: SourceGroup) => dispatch(updateSourceGroup(group)),
addToGroup: (groupIndex: number, sid: number) => dispatch(addSourceToGroup(groupIndex, sid)),
deleteGroup: (groupIndex: number) => dispatch(deleteSourceGroup(groupIndex)),
removeFromGroup: (groupIndex: number, sids: number[]) => dispatch(removeSourceFromGroup(groupIndex, sids)),
reorderGroups: (groups: SourceGroup[]) => dispatch(reorderSourceGroups(groups))
reorderGroups: (groups: SourceGroup[]) => dispatch(reorderSourceGroups(groups)),
importGroups: () => dispatch(importGroups()),
})
const GroupsTabContainer = connect(mapStateToProps, mapDispatchToProps)(GroupsTab)

View File

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src-elem 'self'; img-src *; style-src 'self' 'unsafe-inline'; font-src 'self' https://static2.sharepointonline.com; connect-src https://* http://*">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src-elem 'self'; img-src *; style-src 'self' 'unsafe-inline'; font-src 'self' https://static2.sharepointonline.com; connect-src https: http:">
<title>Fluent Reader</title>
<link rel="stylesheet" href="index.css">
</head>

View File

@ -41,6 +41,7 @@ export const enum SyncService {
}
export interface ServiceConfigs {
type: SyncService
importGroups?: boolean
}
export type SchemaTypes = {

View File

@ -185,7 +185,7 @@
"select": "Select a service",
"suggest": "Suggest a new service",
"overwriteWarning": "Local sources will be deleted if they exist in the service.",
"groupsWarning": "Groups are only imported on the first sync and will not stay synced.",
"groupsWarning": "Groups aren't automatically synced with the service.",
"endpoint": "Endpoint",
"username": "Username",
"password": "Password",

View File

@ -183,7 +183,7 @@
"select": "选择服务",
"suggest": "建议一项新服务",
"overwriteWarning": "若本地与服务端存在URL相同的订阅源则本地订阅源将被删除",
"groupsWarning": "分组仅在第一次同步时导入而不会与服务端保持同步",
"groupsWarning": "分组不会自动与服务端保持同步",
"endpoint": "端点",
"username": "用户名",
"password": "密码",

View File

@ -61,6 +61,18 @@ export function syncWithService(background = false): AppThunk<Promise<void>> {
}
}
export function importGroups(): AppThunk<Promise<void>> {
return async (dispatch, getState) => {
const configs = getState().service
if (configs.type !== SyncService.None) {
dispatch(saveSettings())
configs.importGroups = true
dispatch(saveServiceConfigs(configs))
await dispatch(syncWithService())
}
}
}
export function removeService(): AppThunk<Promise<void>> {
return async (dispatch, getState) => {
dispatch(saveSettings())

View File

@ -17,7 +17,7 @@ export interface FeverConfigs extends ServiceConfigs {
apiKey: string
fetchLimit: number
lastId?: number
importGroups?: boolean
useInt32?: boolean
}
async function fetchAPI(configs: FeverConfigs, params="", postparams="") {
@ -150,14 +150,24 @@ export const feverServiceHooks: ServiceHooks = {
const configs = state.service as FeverConfigs
const items = new Array()
configs.lastId = configs.lastId || 0
let min = 2147483647
let min = configs.useInt32 ? 2147483647 : Number.MAX_SAFE_INTEGER
let response
do {
response = await fetchAPI(configs, `&items&max_id=${min}`)
if (response.items === undefined) throw APIError()
items.push(...response.items.filter(i => i.id > configs.lastId))
min = response.items.reduce((m, n) => Math.min(m, n.id), min)
} while (min > configs.lastId && response.items.length >= 50 && items.length < configs.fetchLimit)
if (response.items.length === 0 && min === Number.MAX_SAFE_INTEGER) {
configs.useInt32 = true
min = 2147483647
response = undefined
} else {
min = response.items.reduce((m, n) => Math.min(m, n.id), min)
}
} while (
min > configs.lastId &&
(response === undefined || response.items.length >= 50) &&
items.length < configs.fetchLimit
)
configs.lastId = items.reduce((m, n) => Math.max(m, n.id), configs.lastId)
if (items.length > 0) {
const fidMap = new Map<number, RSSSource>()
@ -178,7 +188,7 @@ export const feverServiceHooks: ServiceHooks = {
snippet: htmlDecode(i.html).trim(),
creator: i.author,
hasRead: Boolean(i.is_read),
serviceRef: i.id
serviceRef: typeof i.id === "string" ? parseInt(i.id) : i.id,
} as RSSItem
if (i.is_saved) item.starred = true
// Try to get the thumbnail of the item