From 69f5a21038bb66f1be4301c3cc1e11ee8c6bb615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B5=A9=E8=BF=9C?= Date: Fri, 12 Jun 2020 10:18:44 +0800 Subject: [PATCH] use electron-store --- dist/article/article.css | 14 +++--- dist/article/article.html | 4 +- dist/article/article.js | 1 - dist/article/scroll.css | 18 ++++--- dist/styles.css | 95 +++++++++++++++++++----------------- src/components/article.tsx | 18 +++++-- src/electron.ts | 5 -- src/index.html | 2 +- src/scripts/config-schema.ts | 22 +++++++++ src/scripts/models/app.ts | 3 +- src/scripts/models/group.ts | 6 +-- src/scripts/settings.ts | 53 +++++++++----------- 12 files changed, 134 insertions(+), 107 deletions(-) create mode 100644 src/scripts/config-schema.ts diff --git a/dist/article/article.css b/dist/article/article.css index 015cd8b..10d7b27 100644 --- a/dist/article/article.css +++ b/dist/article/article.css @@ -4,17 +4,19 @@ html, body { html { overflow: hidden scroll; } -body { +:root { margin: 12px 96px 32px; --gray: #484644; --primary: #0078d4; --primary-alt: #004578; } -body.dark { - color: #f8f8f8; - --gray: #a19f9d; - --primary: #4ba0e1; - --primary-alt: #65aee6; +@media (prefers-color-scheme: dark) { + :root { + color: #f8f8f8; + --gray: #a19f9d; + --primary: #4ba0e1; + --primary-alt: #65aee6; + } } a { diff --git a/dist/article/article.html b/dist/article/article.html index 047c265..71bd395 100644 --- a/dist/article/article.html +++ b/dist/article/article.html @@ -3,14 +3,14 @@ + content="default-src 'none'; script-src-elem 'sha256-34JRfFnY5YiFDB1MABAIxq6OfvlgM/Ba2el4MdA0WoM='; img-src http://* https://*; style-src 'self' 'unsafe-inline'; frame-src http://* https://*; media-src http://* https://*"> Article
- + \ No newline at end of file diff --git a/dist/article/article.js b/dist/article/article.js index c67a6ea..03213c1 100644 --- a/dist/article/article.js +++ b/dist/article/article.js @@ -15,7 +15,6 @@ for (let i of dom.querySelectorAll("img")) { for (let s of dom.querySelectorAll("script")) { s.parentNode.removeChild(s) } -if (get("d") === "1") document.body.classList.add("dark") let main = document.getElementById("main") main.innerHTML = dom.body.innerHTML document.addEventListener("click", event => { diff --git a/dist/article/scroll.css b/dist/article/scroll.css index 6bf930e..0e6aa64 100644 --- a/dist/article/scroll.css +++ b/dist/article/scroll.css @@ -12,12 +12,14 @@ ::-webkit-scrollbar-thumb:active { background-color: #0008; } -body.dark::-webkit-scrollbar-thumb , body.dark *::-webkit-scrollbar-thumb { - background-color: #fff4; +@media (prefers-color-scheme: dark) { + ::-webkit-scrollbar-thumb { + background-color: #fff4; + } + ::-webkit-scrollbar-thumb:hover { + background-color: #fff6; + } + ::-webkit-scrollbar-thumb:active { + background-color: #fff8; + } } -body.dark::-webkit-scrollbar-thumb:hover, body.dark *::-webkit-scrollbar-thumb:hover { - background-color: #fff6; -} -body.dark::-webkit-scrollbar-thumb:active, body.dark *::-webkit-scrollbar-thumb:active { - background-color: #fff8; -} \ No newline at end of file diff --git a/dist/styles.css b/dist/styles.css index a6d33e3..2615609 100644 --- a/dist/styles.css +++ b/dist/styles.css @@ -16,25 +16,28 @@ --white: #fff; --whiteConstant: #fff; } -body.dark { - --neutralLighterAlt: #282828; - --neutralLighter: #313131; - --neutralLight: #3f3f3f; - --neutralQuaternaryAlt: #484848; - --neutralQuaternary: #4f4f4f; - --neutralTertiaryAlt: #6d6d6d; - --neutralTertiary: #c8c8c8; - --neutralSecondaryAlt: #d2d0ce; - --neutralSecondary: #d0d0d0; - --neutralPrimaryAlt: #dadada; - --neutralPrimary: #ffffff; - --neutralDark: #f4f4f4; - --neutralDarker: #f4f4f4; - --black: #f8f8f8; - --white: #1f1f1f; - --whiteConstant: #f8f8f8; +@media (prefers-color-scheme: dark) { + :root { + --neutralLighterAlt: #282828; + --neutralLighter: #313131; + --neutralLight: #3f3f3f; + --neutralQuaternaryAlt: #484848; + --neutralQuaternary: #4f4f4f; + --neutralTertiaryAlt: #6d6d6d; + --neutralTertiary: #c8c8c8; + --neutralSecondaryAlt: #d2d0ce; + --neutralSecondary: #d0d0d0; + --neutralPrimaryAlt: #dadada; + --neutralPrimary: #ffffff; + --neutralDark: #f4f4f4; + --neutralDarker: #f4f4f4; + --black: #f8f8f8; + --white: #1f1f1f; + --whiteConstant: #f8f8f8; + } } + html, body { background-color: transparent; font-family: "Segoe UI Regular", "Source Han Sans SC Regular", "Microsoft YaHei", sans-serif; @@ -79,9 +82,6 @@ body { .ms-Button--commandBar.active .ms-Button-icon { color: #005a9e; } -body.dark .ms-Button--commandBar.active .ms-Button-icon { - color: #c7e0f4; -} i.ms-Nav-chevron { line-height: 32px; height: 32px; @@ -186,12 +186,6 @@ nav.menu-on .btn-group .btn.system, nav.item-on .btn-group .btn.system { .btn-group .btn:active { background-color: #0002; } -body.dark .btn-group .btn:hover { - background-color: #fff1; -} -body.dark .btn-group .btn:active { - background-color: #fff2; -} .btn-group .btn.disabled, .btn-group .btn.fetching { background-color: unset !important; color: var(--neutralSecondaryAlt); @@ -282,9 +276,6 @@ div[role="tabpanel"] { background-color: #fffa; z-index: 6; } -body.dark .settings .loading { - background-color: #000a; -} .settings .loading .ms-Spinner { margin-top: 180px; } @@ -590,12 +581,7 @@ img.favicon { .card:active { transform: scale(.97); } -body.dark .card { - box-shadow: #0006 0px 5px 20px; -} -body.dark .card:hover { - box-shadow: #0008 0px 5px 40px; -} + .card .bg { position: absolute; left: 0; @@ -610,9 +596,6 @@ body.dark .card:hover { .card div.bg { background-color: #fffb; } -body.dark div.bg { - background-color: #000b; -} .card img.head { display: block; object-fit: cover; @@ -684,12 +667,6 @@ body.dark div.bg { .list-card:active { box-shadow: #0000 0px 5px 15px, inset #0004 0px 0px 15px; } -body.dark .list-card:hover { - box-shadow: #0006 0px 5px 15px; -} -body.dark .list-card:active { - box-shadow: #0000 0px 5px 15px, inset #0006 0px 0px 15px; -} .list-card div.head { width: 80px; height: 80px; @@ -719,3 +696,33 @@ body.dark .list-card:active { display: -webkit-box; -webkit-box-orient: vertical; } + +@media (prefers-color-scheme: dark) { + .ms-Button--commandBar.active .ms-Button-icon { + color: #c7e0f4; + } + .btn-group .btn:hover { + background-color: #fff1; + } + .btn-group .btn:active { + background-color: #fff2; + } + .settings .loading { + background-color: #000a; + } + .card { + box-shadow: #0006 0px 5px 20px; + } + .card:hover { + box-shadow: #0008 0px 5px 40px; + } + .card div.bg { + background-color: #000b; + } + .list-card:hover { + box-shadow: #0006 0px 5px 15px; + } + .list-card:active { + box-shadow: #0000 0px 5px 15px, inset #0006 0px 0px 15px; + } +} \ No newline at end of file diff --git a/src/components/article.tsx b/src/components/article.tsx index 3b3929f..d6c57db 100644 --- a/src/components/article.tsx +++ b/src/components/article.tsx @@ -5,6 +5,8 @@ import { RSSItem } from "../scripts/models/item" import { openExternal } from "../scripts/utils" import { Stack, CommandBarButton, IContextualMenuProps } from "@fluentui/react" import { RSSSource, SourceOpenTarget } from "../scripts/models/source" +import { store } from "../scripts/settings" +import { clipboard } from "electron" const FONT_SIZE_STORE_KEY = "fontSize" const FONT_SIZE_OPTIONS = [12, 13, 14, 15, 16, 17, 18, 19, 20] @@ -37,11 +39,10 @@ class Article extends React.Component { } getFontSize = () => { - let size = window.localStorage.getItem(FONT_SIZE_STORE_KEY) - return size ? parseInt(size) : 16 + return store.get(FONT_SIZE_STORE_KEY, 16) } setFontSize = (size: number) => { - window.localStorage.setItem(FONT_SIZE_STORE_KEY, String(size)) + store.set(FONT_SIZE_STORE_KEY, size) this.setState({fontSize: size}) } @@ -60,12 +61,19 @@ class Article extends React.Component { { key: "openInBrowser", text: intl.get("openExternal"), - iconProps: {iconName: "NavigateExternalInline"}, + iconProps: { iconName: "NavigateExternalInline" }, onClick: this.openInBrowser }, + { + key: "copyURL", + text: intl.get("context.copyURL"), + iconProps: { iconName: "Link" }, + onClick: () => { clipboard.writeText(this.props.item.link) } + }, { key: "toggleHidden", text: this.props.item.hidden ? intl.get("article.unhide") : intl.get("article.hide"), + iconProps: { iconName: this.props.item.hidden ? "View" : "Hide3" }, onClick: () => { this.props.toggleHidden(this.props.item) } } ] @@ -132,7 +140,7 @@ class Article extends React.Component {

{this.props.item.title}

{this.props.item.date.toLocaleString(this.props.locale, {hour12: !this.props.locale.startsWith("zh")})}

- ))) + `&s=${this.state.fontSize}&u=${this.props.item.link}&d=${Number(document.body.classList.contains("dark"))}` + ))) + `&s=${this.state.fontSize}&u=${this.props.item.link}` render = () => (
diff --git a/src/electron.ts b/src/electron.ts index e55728b..237e73a 100644 --- a/src/electron.ts +++ b/src/electron.ts @@ -55,8 +55,3 @@ app.on('activate', function () { createWindow() } }) - -ipcMain.on("set-theme", (_, theme) => { - store.set("theme", theme) - nativeTheme.themeSource = theme -}) diff --git a/src/index.html b/src/index.html index 24a10f4..d85a206 100644 --- a/src/index.html +++ b/src/index.html @@ -2,7 +2,7 @@ - + Fluent Reader diff --git a/src/scripts/config-schema.ts b/src/scripts/config-schema.ts new file mode 100644 index 0000000..05d3ff4 --- /dev/null +++ b/src/scripts/config-schema.ts @@ -0,0 +1,22 @@ +import { ThemeSettings } from "./settings" +import { SourceGroup } from "./models/group" + +// Using schema breaks unsafe-eval. Unused. +/* export const schema: {[key in keyof schemaTypes]: Schema} = { + theme: { type: "string", default: "system" }, + pac: { type: "string", default: "" }, + pacOn: { type: "boolean", default: false }, + view: { type: "number", default: 0 }, + locale: { type: "string", default: "default" }, + sourceGroups: { type: "array", default: [] } +} */ + +export type schemaTypes = { + theme: ThemeSettings + pac: string + pacOn: boolean + view: number + locale: string + sourceGroups: SourceGroup[] + fontSize: number +} diff --git a/src/scripts/models/app.ts b/src/scripts/models/app.ts index e2acb7e..6cd17c9 100644 --- a/src/scripts/models/app.ts +++ b/src/scripts/models/app.ts @@ -3,7 +3,7 @@ import { RSSSource, INIT_SOURCES, SourceActionTypes, ADD_SOURCE, UPDATE_SOURCE, import { RSSItem, ItemActionTypes, FETCH_ITEMS, fetchItems } from "./item" import { ActionStatus, AppThunk, getWindowBreakpoint } from "../utils" import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed" -import { SourceGroupActionTypes, UPDATE_SOURCE_GROUP, ADD_SOURCE_TO_GROUP, DELETE_SOURCE_GROUP, REMOVE_SOURCE_FROM_GROUP } from "./group" +import { SourceGroupActionTypes, UPDATE_SOURCE_GROUP, ADD_SOURCE_TO_GROUP, DELETE_SOURCE_GROUP, REMOVE_SOURCE_FROM_GROUP, REORDER_SOURCE_GROUPS } from "./group" import { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles } from "./page" import { getCurrentLocale, setLocaleSettings } from "../settings" import locales from "../i18n/_locales" @@ -230,6 +230,7 @@ export function appReducer( case UPDATE_SOURCE_GROUP: case ADD_SOURCE_TO_GROUP: case REMOVE_SOURCE_FROM_GROUP: + case REORDER_SOURCE_GROUPS: case DELETE_SOURCE_GROUP: return { ...state, settings: { diff --git a/src/scripts/models/group.ts b/src/scripts/models/group.ts index 662e049..77e27fd 100644 --- a/src/scripts/models/group.ts +++ b/src/scripts/models/group.ts @@ -3,6 +3,7 @@ import { SourceActionTypes, ADD_SOURCE, DELETE_SOURCE, addSource } from "./sourc import { ActionStatus, AppThunk, domParser, AppDispatch } from "../utils" import { saveSettings } from "./app" +import { store } from "../settings" const GROUPS_STORE_KEY = "sourceGroups" @@ -24,12 +25,11 @@ export class SourceGroup { } static save(groups: SourceGroup[]) { - localStorage.setItem(GROUPS_STORE_KEY, JSON.stringify(groups)) + store.set(GROUPS_STORE_KEY, groups) } static load(): SourceGroup[] { - let stored = localStorage.getItem(GROUPS_STORE_KEY) - return stored ? JSON.parse(stored) : [] + return store.get(GROUPS_STORE_KEY, []) } } diff --git a/src/scripts/settings.ts b/src/scripts/settings.ts index 86b586f..76b4009 100644 --- a/src/scripts/settings.ts +++ b/src/scripts/settings.ts @@ -2,24 +2,28 @@ import { remote, ipcRenderer } from "electron" import { ViewType } from "./models/page" import { IPartialTheme, loadTheme } from "@fluentui/react" import locales from "./i18n/_locales" +import Store = require("electron-store") +import { schemaTypes } from "./config-schema" -const PAC_STORE_KEY = "PAC" -const PAC_STATUS_KEY = "PAC_ON" +export const store = new Store() + +const PAC_STORE_KEY = "pac" +const PAC_STATUS_KEY = "pacOn" export function getProxyStatus() { - return Boolean(localStorage.getItem(PAC_STATUS_KEY)) + return store.get(PAC_STATUS_KEY, false) } export function toggleProxyStatus() { - localStorage.setItem(PAC_STATUS_KEY, getProxyStatus() ? "" : "on") + store.set(PAC_STATUS_KEY, !getProxyStatus()) setProxy() } export function getProxy() { - return localStorage.getItem(PAC_STORE_KEY) || "" + return store.get(PAC_STORE_KEY, "") } export function setProxy(address = null) { if (!address) { address = getProxy() } else { - localStorage.setItem(PAC_STORE_KEY, address) + store.set(PAC_STORE_KEY, address) } remote.getCurrentWebContents().session.setProxy({ pacScript: getProxyStatus() ? address : "" @@ -30,12 +34,11 @@ export function setProxy(address = null) { } const VIEW_STORE_KEY = "view" -export const getDefaultView = () => { - let view = localStorage.getItem(VIEW_STORE_KEY) - return view ? parseInt(view) as ViewType : ViewType.Cards +export const getDefaultView = (): ViewType => { + return store.get(VIEW_STORE_KEY, ViewType.Cards) } export const setDefaultView = (viewType: ViewType) => { - localStorage.setItem(VIEW_STORE_KEY, String(viewType)) + store.set(VIEW_STORE_KEY, viewType) } const lightTheme: IPartialTheme = { @@ -77,41 +80,29 @@ export enum ThemeSettings { } const THEME_STORE_KEY = "theme" export function setThemeSettings(theme: ThemeSettings) { - localStorage.setItem(THEME_STORE_KEY, theme) - ipcRenderer.send("set-theme", theme) + store.set(THEME_STORE_KEY, theme) + remote.nativeTheme.themeSource = theme applyThemeSettings() } export function getThemeSettings(): ThemeSettings { - let stored = localStorage.getItem(THEME_STORE_KEY) - return stored === null ? ThemeSettings.Default : stored as ThemeSettings + return store.get(THEME_STORE_KEY, ThemeSettings.Default) } export function applyThemeSettings() { - let theme = getThemeSettings() - let useDark = theme === ThemeSettings.Default - ? remote.nativeTheme.shouldUseDarkColors - : theme === ThemeSettings.Dark - loadTheme(useDark ? darkTheme : lightTheme) - if (useDark) { - document.body.classList.add("dark") - } else { - document.body.classList.remove("dark") - } + loadTheme(remote.nativeTheme.shouldUseDarkColors ? darkTheme : lightTheme) } +remote.nativeTheme.on("updated", () => { + applyThemeSettings() +}) const LOCALE_STORE_KEY = "locale" export function setLocaleSettings(option: string) { - localStorage.setItem(LOCALE_STORE_KEY, option) + store.set(LOCALE_STORE_KEY, option) } export function getLocaleSettings() { - let stored = localStorage.getItem(LOCALE_STORE_KEY) - return stored === null ? "default" : stored + return store.get(LOCALE_STORE_KEY, "default") } export function getCurrentLocale() { let set = getLocaleSettings() let locale = set === "default" ? remote.app.getLocale() : set return (locale in locales) ? locale : "en-US" } - -export const STORE_KEYS = [ - PAC_STORE_KEY, PAC_STATUS_KEY, VIEW_STORE_KEY, THEME_STORE_KEY -] \ No newline at end of file