mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-02-01 01:47:07 +01:00
commit
c52b3bf00c
3
dist/styles/global.css
vendored
3
dist/styles/global.css
vendored
@ -131,6 +131,9 @@ i.ms-Nav-chevron {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
body.blur #root > nav {
|
||||
--black: var(--neutralSecondaryAlt);
|
||||
}
|
||||
nav .progress {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
3
dist/styles/main.css
vendored
3
dist/styles/main.css
vendored
@ -26,6 +26,9 @@
|
||||
height: 100%;
|
||||
background-color: var(--neutralLighterAlt);
|
||||
}
|
||||
body.blur .menu .btn-group {
|
||||
--black: var(--neutralSecondaryAlt);
|
||||
}
|
||||
body.darwin .menu .btn-group {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
|
163
package-lock.json
generated
163
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fluent-reader",
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -46,56 +46,66 @@
|
||||
"sumchecker": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"@fluentui/date-time-utilities": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/date-time-utilities/-/date-time-utilities-7.4.0.tgz",
|
||||
"integrity": "sha512-8zaFJ5I1AikQmoi5aWv/mustCf8UAFYUjJrrnlXwvXOe2HlC+wLZH236qRZPi4Wat8qG151vE24nTqzGMVldRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@uifabric/set-version": "^7.0.19",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@fluentui/keyboard-key": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/keyboard-key/-/keyboard-key-0.2.1.tgz",
|
||||
"integrity": "sha512-s2CYcspWWdqzwXNOvkNURifuRRiZun/5CQ3gcvRw9+S9/ONvPtedRkppNeTyj2wbW6Ctzf218bu2eJqu0aVK/Q==",
|
||||
"version": "0.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/keyboard-key/-/keyboard-key-0.2.8.tgz",
|
||||
"integrity": "sha512-GJW3NjDdigTddYuxoOuBGhOs5Egweqs6iPTDSUN+oAtXI/poYHVtgjxaFQx1OeAzD8wLXofGneAe/03ZW+TESA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@fluentui/react": {
|
||||
"version": "7.117.1",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react/-/react-7.117.1.tgz",
|
||||
"integrity": "sha512-BKXFdZPjeOVdQksfLVM4YasDYk5LByTw7rGrW/z+Ae4RWRZ9JnLrfaPZSUSn4Puu5iKqipp3+WSU6BXcaPdvPA==",
|
||||
"version": "7.126.2",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react/-/react-7.126.2.tgz",
|
||||
"integrity": "sha512-WQ4u1oV0Cm+N2igltRIpg/B8LpxHiaiLue9++aZTtCvz4I5I5dRjm+LlVEomV5ml3n4m5apg9ml2MpNTQ7S+cA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@uifabric/set-version": "^7.0.13",
|
||||
"office-ui-fabric-react": "^7.117.1",
|
||||
"@uifabric/set-version": "^7.0.19",
|
||||
"office-ui-fabric-react": "^7.126.2",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@fluentui/react-focus": {
|
||||
"version": "7.12.5",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-7.12.5.tgz",
|
||||
"integrity": "sha512-K2DrfMI74pkuGeiqstWgqGVnMg836CE3eJ3fydkXa19+6lUjkB4yDrSgwrQkvvEXixCiJyfvAxyGuFJ74EKQwA==",
|
||||
"version": "7.12.30",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-7.12.30.tgz",
|
||||
"integrity": "sha512-UV9StYMID9cRB2TQRE1u134sT8AOaOs0A2ZW4oM82/lzQ8XxL3aREFY0ohhmblYhEGzkb349y51h4eSqW5EpPA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@fluentui/keyboard-key": "^0.2.1",
|
||||
"@uifabric/merge-styles": "^7.14.1",
|
||||
"@uifabric/set-version": "^7.0.13",
|
||||
"@uifabric/styling": "^7.12.15",
|
||||
"@uifabric/utilities": "^7.20.3",
|
||||
"@fluentui/keyboard-key": "^0.2.8",
|
||||
"@uifabric/merge-styles": "^7.16.4",
|
||||
"@uifabric/set-version": "^7.0.19",
|
||||
"@uifabric/styling": "^7.14.10",
|
||||
"@uifabric/utilities": "^7.26.1",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@fluentui/react-icons": {
|
||||
"version": "0.1.24",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react-icons/-/react-icons-0.1.24.tgz",
|
||||
"integrity": "sha512-PNAN1CjfBNL6p0fViftKDK8xcVnt05psEhO9wzCSOld9iw2eGu3JEObzolKGGoASvsiPJEQxbGOHZPuasVKVFg==",
|
||||
"version": "0.1.45",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react-icons/-/react-icons-0.1.45.tgz",
|
||||
"integrity": "sha512-xDE7dgbh3JgUt2uFUW66ut4V9T5irmxi+S5IGJKkXLUf3MHBysknx42BkX1Yc1Uczz3r5va1oWWwlXNiN/CjsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@uifabric/set-version": "^7.0.13",
|
||||
"@uifabric/styling": "^7.12.15",
|
||||
"@uifabric/utilities": "^7.20.3",
|
||||
"@microsoft/load-themed-styles": "^1.10.26",
|
||||
"@uifabric/set-version": "^7.0.19",
|
||||
"@uifabric/utilities": "^7.26.1",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@microsoft/load-themed-styles": {
|
||||
"version": "1.10.55",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.10.55.tgz",
|
||||
"integrity": "sha512-1gT/zQVJC6dTvMAHfxteTlBmGsSHuLkKVe5vYCQ6JOzah8Yv9hwTSZESUZfj5HKV2PQ11KnVrT3TS815gKExoA==",
|
||||
"version": "1.10.66",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.10.66.tgz",
|
||||
"integrity": "sha512-w1NCJQOrr5Ko5Og1ay7NO0vFUwxksddnLmVuY2bBi7DkgbFYtiRFQGkc+HMDpy2x6Fcy/iUTitbR674aX5TJhw==",
|
||||
"dev": true
|
||||
},
|
||||
"@sindresorhus/is": {
|
||||
@ -278,80 +288,80 @@
|
||||
"dev": true
|
||||
},
|
||||
"@uifabric/foundation": {
|
||||
"version": "7.7.22",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/foundation/-/foundation-7.7.22.tgz",
|
||||
"integrity": "sha512-vTWhNT0wz0VB5DagSCPWQ+7d8Yv7AYOGsvOgPzS6FzA3pxSLX6xmN00ica1gCCZBz4xZn4Yw7YjcyF8bDARqSw==",
|
||||
"version": "7.7.44",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/foundation/-/foundation-7.7.44.tgz",
|
||||
"integrity": "sha512-0YBZTGsVxQEd1+IYXawQcOd263auoXCQo2KE8CaL2aug330+9LIqloIyjnhDH+yJQ9JZZmzzlJ2clOD7NCT97g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@uifabric/merge-styles": "^7.14.1",
|
||||
"@uifabric/set-version": "^7.0.13",
|
||||
"@uifabric/styling": "^7.12.15",
|
||||
"@uifabric/utilities": "^7.20.3",
|
||||
"@uifabric/merge-styles": "^7.16.4",
|
||||
"@uifabric/set-version": "^7.0.19",
|
||||
"@uifabric/styling": "^7.14.10",
|
||||
"@uifabric/utilities": "^7.26.1",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@uifabric/icons": {
|
||||
"version": "7.3.48",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-7.3.48.tgz",
|
||||
"integrity": "sha512-DUhRluQrYAvvyElr5F/Gzkscl+gArgeEtLVJsjqXAsop8SAvJCEVCnSxVEZVDOOlsytEVVPpxiHGkaDow+26fg==",
|
||||
"version": "7.3.70",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-7.3.70.tgz",
|
||||
"integrity": "sha512-ibAyKU02TFH6wdqrnu7keea9MaM29EGrMTkz24DyNlWfM+H9z3JL6UiWUeldgyok8HXDWI9GRQWB7f3YhJi60Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@uifabric/set-version": "^7.0.13",
|
||||
"@uifabric/styling": "^7.12.15",
|
||||
"@uifabric/set-version": "^7.0.19",
|
||||
"@uifabric/styling": "^7.14.10",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@uifabric/merge-styles": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/merge-styles/-/merge-styles-7.14.1.tgz",
|
||||
"integrity": "sha512-nKkk0o9XyVh8HL174ZSDqw3IUnN2qb+kO73vg/rwioKPEQyuPGoEfij8jrb+CGpcCGnMYi53IeB1tm8ySlNatg==",
|
||||
"version": "7.16.4",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/merge-styles/-/merge-styles-7.16.4.tgz",
|
||||
"integrity": "sha512-OhOEtwYD74AARf4VZQJPan97QEvtTYcxBGVQfdE7YxFnvR1VdfMxOsV+9CAjAIFM+Xu5ibeKkEE/ZmJYnHkqsQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@uifabric/set-version": "^7.0.13",
|
||||
"@uifabric/set-version": "^7.0.19",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@uifabric/react-hooks": {
|
||||
"version": "7.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/react-hooks/-/react-hooks-7.4.5.tgz",
|
||||
"integrity": "sha512-OLEBII+7x4rlTWjQ6hvMWS+CuWRJgvEfCsUcFMu5D5teXTr0bZQThyW8oVvkqKsNmF2JdwwqT6dSwhwgarlFzA==",
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/react-hooks/-/react-hooks-7.7.3.tgz",
|
||||
"integrity": "sha512-WAhMcnQSRgSQr0wkw8paESqxHCDWL2vdgTcDNKSj550GpAzD9BgZSBp3v3yln/qA4pN/nfjeU0CX9HujidSbMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@uifabric/set-version": "^7.0.13",
|
||||
"@uifabric/utilities": "^7.20.3",
|
||||
"@uifabric/set-version": "^7.0.19",
|
||||
"@uifabric/utilities": "^7.26.1",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@uifabric/set-version": {
|
||||
"version": "7.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/set-version/-/set-version-7.0.13.tgz",
|
||||
"integrity": "sha512-SRsYaacvNykS9lRwKNJgrJuhPV4ytblthFNg0+Wi6+zvIf/w50k/nBlmXVetV5U9dAuX4njSkd+/3iOpgevkyw==",
|
||||
"version": "7.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/set-version/-/set-version-7.0.19.tgz",
|
||||
"integrity": "sha512-p52z9Z5Kfl0kAU3DiPNPg+0vCdSAxlkRZEtEa+RwM6fh9XSo91n4C56FFdKDW7HJVuhGjMK7UEXuU6ELY1W7fg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@uifabric/styling": {
|
||||
"version": "7.12.15",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-7.12.15.tgz",
|
||||
"integrity": "sha512-mp/nRyE5Ig6a8BPEkXy/F8ckeQptQCm1SOQDPB1VT1PlknHoOXqqTVn2e1e2DGpWcpFIw4dbodqym3rgpUqkjA==",
|
||||
"version": "7.14.10",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-7.14.10.tgz",
|
||||
"integrity": "sha512-hEmXCJJUVr+ykvPVXyvTHS5f2/GMCh1PObajuXgtpZVpJRzA+Rwgg5gBxWYeRNYw+3WZV62f0zAbmYh8ZCRAhQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@microsoft/load-themed-styles": "^1.10.26",
|
||||
"@uifabric/merge-styles": "^7.14.1",
|
||||
"@uifabric/set-version": "^7.0.13",
|
||||
"@uifabric/utilities": "^7.20.3",
|
||||
"@uifabric/merge-styles": "^7.16.4",
|
||||
"@uifabric/set-version": "^7.0.19",
|
||||
"@uifabric/utilities": "^7.26.1",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@uifabric/utilities": {
|
||||
"version": "7.20.3",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/utilities/-/utilities-7.20.3.tgz",
|
||||
"integrity": "sha512-Amg+qdnNKx0yxjoEFHanM2jTCYfCZAlHPDHcS+BCiSwzeQrEPhlOrtZVPJtagNVhXLFiC09uDsmE/BF6dHb+ww==",
|
||||
"version": "7.26.1",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/utilities/-/utilities-7.26.1.tgz",
|
||||
"integrity": "sha512-FX/Gu4XY6YlvBEyTyEeXUOtPpgTy1irHpSAE/vDbDZQlksVNv4FPnVingQZI9T/rA96ivP4q1PUutrb3X3hfsw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@uifabric/merge-styles": "^7.14.1",
|
||||
"@uifabric/set-version": "^7.0.13",
|
||||
"@uifabric/merge-styles": "^7.16.4",
|
||||
"@uifabric/set-version": "^7.0.19",
|
||||
"prop-types": "^15.7.2",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
@ -2248,9 +2258,9 @@
|
||||
}
|
||||
},
|
||||
"electron": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.5.tgz",
|
||||
"integrity": "sha512-bnL9H48LuQ250DML8xUscsKiuSu+xv5umXbpBXYJ0BfvYVmFfNbG3jCfhrsH7aP6UcQKVxOG1R/oQExd0EFneQ==",
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-9.2.0.tgz",
|
||||
"integrity": "sha512-4ecZ3rcGg//Gk4fAK3Jo61T+uh36JhU6HHR/PTujQqQiBw1g4tNPd4R2hGGth2d+7FkRIs5GdRNef7h64fQEMw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@electron/get": "^1.0.1",
|
||||
@ -4753,21 +4763,22 @@
|
||||
}
|
||||
},
|
||||
"office-ui-fabric-react": {
|
||||
"version": "7.117.1",
|
||||
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.117.1.tgz",
|
||||
"integrity": "sha512-AHjlLgBJmrVFOtL04V3qpljXVw2Z8sgcnzl5ZYBR4ZAGMdEtvzJBq5aMbUoQgh3jKHEgpLJfoznd4XWi75FshQ==",
|
||||
"version": "7.126.2",
|
||||
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.126.2.tgz",
|
||||
"integrity": "sha512-1ETz4x5AsBZ36PBLIbL3T++Aso+9WudIsu3Z331UDNwSJF4LjOM50+PY/i7DUb+LSDzmwMwydCpU5yzEndLRBw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@fluentui/react-focus": "^7.12.5",
|
||||
"@fluentui/react-icons": "^0.1.24",
|
||||
"@fluentui/date-time-utilities": "^7.4.0",
|
||||
"@fluentui/react-focus": "^7.12.30",
|
||||
"@fluentui/react-icons": "^0.1.45",
|
||||
"@microsoft/load-themed-styles": "^1.10.26",
|
||||
"@uifabric/foundation": "^7.7.22",
|
||||
"@uifabric/icons": "^7.3.48",
|
||||
"@uifabric/merge-styles": "^7.14.1",
|
||||
"@uifabric/react-hooks": "^7.4.5",
|
||||
"@uifabric/set-version": "^7.0.13",
|
||||
"@uifabric/styling": "^7.12.15",
|
||||
"@uifabric/utilities": "^7.20.3",
|
||||
"@uifabric/foundation": "^7.7.44",
|
||||
"@uifabric/icons": "^7.3.70",
|
||||
"@uifabric/merge-styles": "^7.16.4",
|
||||
"@uifabric/react-hooks": "^7.7.3",
|
||||
"@uifabric/set-version": "^7.0.19",
|
||||
"@uifabric/styling": "^7.14.10",
|
||||
"@uifabric/utilities": "^7.26.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fluent-reader",
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.2",
|
||||
"description": "Modern desktop RSS reader",
|
||||
"main": "./dist/electron.js",
|
||||
"scripts": {
|
||||
@ -83,13 +83,13 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fluentui/react": "^7.115.3",
|
||||
"@fluentui/react": "^7.126.2",
|
||||
"@types/nedb": "^1.8.9",
|
||||
"@types/react": "^16.9.35",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-redux": "^7.1.9",
|
||||
"@yang991178/rss-parser": "^3.8.1",
|
||||
"electron": "^9.0.5",
|
||||
"electron": "^9.2.0",
|
||||
"electron-builder": "^22.7.0",
|
||||
"electron-react-devtools": "^0.5.3",
|
||||
"electron-store": "^5.2.0",
|
||||
|
@ -89,6 +89,13 @@ const settingsBridge = {
|
||||
ipcRenderer.invoke("set-service-configs", configs)
|
||||
},
|
||||
|
||||
getFilterType: (): number => {
|
||||
return ipcRenderer.sendSync("get-filter-type")
|
||||
},
|
||||
setFilterType: (filterType: number) => {
|
||||
ipcRenderer.invoke("set-filter-type", filterType)
|
||||
},
|
||||
|
||||
getAll: () => {
|
||||
return ipcRenderer.sendSync("get-all-settings") as Object
|
||||
},
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ipcRenderer } from "electron"
|
||||
import { ImageCallbackTypes, TouchBarTexts } from "../schema-types"
|
||||
import { ImageCallbackTypes, TouchBarTexts, WindowStateListenerType } from "../schema-types"
|
||||
import { IObjectWithKey } from "@fluentui/react"
|
||||
|
||||
const utilsBridge = {
|
||||
@ -99,21 +99,29 @@ const utilsBridge = {
|
||||
requestAttention: () => {
|
||||
ipcRenderer.invoke("request-attention")
|
||||
},
|
||||
addWindowStateListener: (callback: (state: boolean) => any) => {
|
||||
addWindowStateListener: (callback: (type: WindowStateListenerType, state: boolean) => any) => {
|
||||
ipcRenderer.removeAllListeners("maximized")
|
||||
ipcRenderer.on("maximized", () => {
|
||||
callback(true)
|
||||
callback(WindowStateListenerType.Maximized, true)
|
||||
})
|
||||
ipcRenderer.removeAllListeners("unmaximized")
|
||||
ipcRenderer.on("unmaximized", () => {
|
||||
callback(false)
|
||||
callback(WindowStateListenerType.Maximized, false)
|
||||
})
|
||||
ipcRenderer.removeAllListeners("window-focus")
|
||||
ipcRenderer.on("window-focus", () => {
|
||||
callback(WindowStateListenerType.Focused, true)
|
||||
})
|
||||
ipcRenderer.removeAllListeners("window-blur")
|
||||
ipcRenderer.on("window-blur", () => {
|
||||
callback(WindowStateListenerType.Focused, false)
|
||||
})
|
||||
},
|
||||
|
||||
addTouchBarEventsListener: (callback: (IObjectWithKey) => any) => {
|
||||
ipcRenderer.removeAllListeners("touchbar-event")
|
||||
ipcRenderer.on("touchbar-event", (_, key: string) => {
|
||||
callback({ key: key} )
|
||||
callback({ key: key } )
|
||||
})
|
||||
},
|
||||
initTouchBar: (texts: TouchBarTexts) => {
|
||||
|
@ -5,7 +5,7 @@ import { RSSItem } from "../scripts/models/item"
|
||||
import { Stack, CommandBarButton, IContextualMenuProps, FocusZone, ContextualMenuItemType, Spinner, Icon, Link } from "@fluentui/react"
|
||||
import { RSSSource, SourceOpenTarget } from "../scripts/models/source"
|
||||
import { shareSubmenu } from "./context-menu"
|
||||
import { platformCtrl } from "../scripts/utils"
|
||||
import { platformCtrl, decodeFetchResponse } from "../scripts/utils"
|
||||
|
||||
const FONT_SIZE_OPTIONS = [12, 13, 14, 15, 16, 17, 18, 19, 20]
|
||||
|
||||
@ -214,7 +214,9 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
||||
loadFull = async () => {
|
||||
this.setState({ fullContent: "", loaded: false, error: false })
|
||||
try {
|
||||
const html = await (await fetch(this.props.item.link)).text()
|
||||
const result = await fetch(this.props.item.link)
|
||||
if (!result || !result.ok) throw new Error()
|
||||
const html = await decodeFetchResponse(result, true)
|
||||
this.setState({ fullContent: html })
|
||||
} catch {
|
||||
this.setState({ loaded: true, error: true, errorDescription: "MERCURY_PARSER_FAILURE" })
|
||||
|
@ -2,13 +2,14 @@ import * as React from "react"
|
||||
import { RSSSource, SourceOpenTarget } from "../../scripts/models/source"
|
||||
import { RSSItem } from "../../scripts/models/item"
|
||||
import { platformCtrl } from "../../scripts/utils"
|
||||
import { FeedFilter } from "../../scripts/models/feed"
|
||||
|
||||
export namespace Card {
|
||||
export type Props = {
|
||||
feedId: string
|
||||
item: RSSItem
|
||||
source: RSSSource
|
||||
keyword: string
|
||||
filter: FeedFilter
|
||||
shortcuts: (item: RSSItem, e: KeyboardEvent) => void
|
||||
markRead: (item: RSSItem) => void
|
||||
contextMenu: (feedId: string, item: RSSItem, e) => void
|
||||
|
@ -18,8 +18,8 @@ const CompactCard: React.FunctionComponent<Card.Props> = (props) => (
|
||||
data-is-focusable>
|
||||
<CardInfo source={props.source} item={props.item} hideTime />
|
||||
<div className="data">
|
||||
<span className="title"><Highlights text={props.item.title} keyword={props.keyword} title /></span>
|
||||
<span className="snippet"><Highlights text={props.item.snippet} keyword={props.keyword} /></span>
|
||||
<span className="title"><Highlights text={props.item.title} filter={props.filter} title /></span>
|
||||
<span className="snippet"><Highlights text={props.item.snippet} filter={props.filter} /></span>
|
||||
</div>
|
||||
<Time date={props.item.date} />
|
||||
</div>
|
||||
|
@ -24,9 +24,9 @@ const DefaultCard: React.FunctionComponent<Card.Props> = (props) => (
|
||||
<img className="head" src={props.item.thumb} />
|
||||
) : null}
|
||||
<CardInfo source={props.source} item={props.item} />
|
||||
<h3 className="title"><Highlights text={props.item.title} keyword={props.keyword} title /></h3>
|
||||
<h3 className="title"><Highlights text={props.item.title} filter={props.filter} title /></h3>
|
||||
<p className={"snippet" + (props.item.thumb ? "" : " show")}>
|
||||
<Highlights text={props.item.snippet} keyword={props.keyword} />
|
||||
<Highlights text={props.item.snippet} filter={props.filter} />
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,16 +1,18 @@
|
||||
import * as React from "react"
|
||||
import { validateRegex } from "../../scripts/utils"
|
||||
import { FeedFilter, FilterType } from "../../scripts/models/feed"
|
||||
|
||||
type HighlightsProps = {
|
||||
text: string
|
||||
keyword: string
|
||||
filter: FeedFilter
|
||||
title?: boolean
|
||||
}
|
||||
|
||||
const Highlights: React.FunctionComponent<HighlightsProps> = (props) => {
|
||||
const spans: [string, boolean][] = new Array()
|
||||
const flags = (props.filter.type & FilterType.CaseInsensitive) ? "ig" : "g"
|
||||
let regex: RegExp
|
||||
if (props.keyword === "" || !(regex = validateRegex(props.keyword, "g"))) {
|
||||
if (props.filter.search === "" || !(regex = validateRegex(props.filter.search, flags))) {
|
||||
if (props.title) spans.push([props.text, false])
|
||||
else spans.push([props.text.substr(0, 325), false])
|
||||
} else if (props.title) {
|
||||
|
@ -20,7 +20,7 @@ const ListCard: React.FunctionComponent<Card.Props> = (props) => (
|
||||
) : null}
|
||||
<div className="data">
|
||||
<CardInfo source={props.source} item={props.item} />
|
||||
<h3 className="title"><Highlights text={props.item.title} keyword={props.keyword} title /></h3>
|
||||
<h3 className="title"><Highlights text={props.item.title} filter={props.filter} title /></h3>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -21,8 +21,8 @@ const MagazineCard: React.FunctionComponent<Card.Props> = (props) => (
|
||||
) : null}
|
||||
<div className="data">
|
||||
<div>
|
||||
<h3 className="title"><Highlights text={props.item.title} keyword={props.keyword} title /></h3>
|
||||
<p className="snippet"><Highlights text={props.item.snippet} keyword={props.keyword} /></p>
|
||||
<h3 className="title"><Highlights text={props.item.title} filter={props.filter} title /></h3>
|
||||
<p className="snippet"><Highlights text={props.item.snippet} filter={props.filter} /></p>
|
||||
</div>
|
||||
<CardInfo source={props.source} item={props.item} showCreator />
|
||||
</div>
|
||||
|
@ -289,11 +289,30 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "fullSearch",
|
||||
text: intl.get("context.fullSearch"),
|
||||
canCheck: true,
|
||||
checked: Boolean(this.props.filter & FilterType.FullSearch),
|
||||
onClick: () => this.props.toggleFilter(FilterType.FullSearch)
|
||||
key: "section_3",
|
||||
itemType: ContextualMenuItemType.Section,
|
||||
sectionProps: {
|
||||
title: intl.get("search"),
|
||||
bottomDivider: true,
|
||||
items: [
|
||||
{
|
||||
key: "caseSensitive",
|
||||
text: intl.get("context.caseSensitive"),
|
||||
iconProps: { style: { fontSize: 12, fontStyle: "normal" }, children: "Aa" },
|
||||
canCheck: true,
|
||||
checked: !(this.props.filter & FilterType.CaseInsensitive),
|
||||
onClick: () => this.props.toggleFilter(FilterType.CaseInsensitive)
|
||||
},
|
||||
{
|
||||
key: "fullSearch",
|
||||
text: intl.get("context.fullSearch"),
|
||||
iconProps: { iconName: "Breadcrumb" },
|
||||
canCheck: true,
|
||||
checked: Boolean(this.props.filter & FilterType.FullSearch),
|
||||
onClick: () => this.props.toggleFilter(FilterType.FullSearch)
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "showHidden",
|
||||
|
@ -47,26 +47,43 @@ class CardsFeed extends React.Component<FeedProps> {
|
||||
key={item._id}
|
||||
item={item}
|
||||
source={this.props.sourceMap[item.source]}
|
||||
keyword={this.props.keyword}
|
||||
filter={this.props.filter}
|
||||
shortcuts={this.props.shortcuts}
|
||||
markRead={this.props.markRead}
|
||||
contextMenu={this.props.contextMenu}
|
||||
showItem={this.props.showItem} />
|
||||
) : (<div className="flex-fix" key={"f-"+index}></div>)
|
||||
|
||||
canFocusChild = (el: HTMLElement) => {
|
||||
if (el.id === "load-more") {
|
||||
const container = document.getElementById("refocus")
|
||||
const result = container.scrollTop > container.scrollHeight - 2 * container.offsetHeight
|
||||
if (!result) container.scrollTop += 100
|
||||
return result
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.feed.loaded && (
|
||||
<FocusZone as="div" id="refocus" className="cards-feed-container" data-is-scrollable>
|
||||
<FocusZone as="div"
|
||||
id="refocus"
|
||||
className="cards-feed-container"
|
||||
shouldReceiveFocus={this.canFocusChild}
|
||||
data-is-scrollable>
|
||||
<List
|
||||
className={AnimationClassNames.slideUpIn10}
|
||||
items={this.flexFixItems()}
|
||||
onRenderCell={this.onRenderItem}
|
||||
getItemCountForPage={this.getItemCountForPage}
|
||||
getPageHeight={this.getPageHeight}
|
||||
ignoreScrollingState
|
||||
usePageCache />
|
||||
{
|
||||
(this.props.feed.loaded && !this.props.feed.allLoaded)
|
||||
? <div className="load-more-wrapper"><PrimaryButton
|
||||
id="load-more"
|
||||
text={intl.get("loadMore")}
|
||||
disabled={this.props.feed.loading}
|
||||
onClick={() => this.props.loadMore(this.props.feed)} /></div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as React from "react"
|
||||
import { RSSItem } from "../../scripts/models/item"
|
||||
import { FeedReduxProps } from "../../containers/feed-container"
|
||||
import { RSSFeed } from "../../scripts/models/feed"
|
||||
import { RSSFeed, FeedFilter } from "../../scripts/models/feed"
|
||||
import { ViewType } from "../../schema-types"
|
||||
import CardsFeed from "./cards-feed"
|
||||
import ListFeed from "./list-feed"
|
||||
@ -11,7 +11,7 @@ export type FeedProps = FeedReduxProps & {
|
||||
viewType: ViewType
|
||||
items: RSSItem[]
|
||||
sourceMap: Object
|
||||
keyword: string
|
||||
filter: FeedFilter
|
||||
shortcuts: (item: RSSItem, e: KeyboardEvent) => void
|
||||
markRead: (item: RSSItem) => void
|
||||
contextMenu: (feedId: string, item: RSSItem, e) => void
|
||||
|
@ -16,7 +16,7 @@ class ListFeed extends React.Component<FeedProps> {
|
||||
key: item._id,
|
||||
item: item,
|
||||
source: this.props.sourceMap[item.source],
|
||||
keyword: this.props.keyword,
|
||||
filter: this.props.filter,
|
||||
shortcuts: this.props.shortcuts,
|
||||
markRead: this.props.markRead,
|
||||
contextMenu: this.props.contextMenu,
|
||||
@ -38,21 +38,35 @@ class ListFeed extends React.Component<FeedProps> {
|
||||
}
|
||||
}
|
||||
|
||||
canFocusChild = (el: HTMLElement) => {
|
||||
if (el.id === "load-more") {
|
||||
const container = document.getElementById("refocus")
|
||||
const result = container.scrollTop > container.scrollHeight - 2 * container.offsetHeight
|
||||
if (!result) container.scrollTop += 100
|
||||
return result
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.feed.loaded && (
|
||||
<FocusZone as="div"
|
||||
id="refocus"
|
||||
direction={FocusZoneDirection.vertical}
|
||||
className={this.getClassName()}
|
||||
shouldReceiveFocus={this.canFocusChild}
|
||||
data-is-scrollable>
|
||||
<List
|
||||
className={AnimationClassNames.slideUpIn10}
|
||||
items={this.props.items}
|
||||
onRenderCell={this.onRenderItem}
|
||||
ignoreScrollingState
|
||||
usePageCache />
|
||||
{
|
||||
(this.props.feed.loaded && !this.props.feed.allLoaded)
|
||||
? <div className="load-more-wrapper"><PrimaryButton
|
||||
id="load-more"
|
||||
text={intl.get("loadMore")}
|
||||
disabled={this.props.feed.loading}
|
||||
onClick={() => this.props.loadMore(this.props.feed)} /></div>
|
||||
|
@ -50,16 +50,16 @@ export class Menu extends React.Component<MenuProps> {
|
||||
},
|
||||
{
|
||||
name: intl.get("menu.subscriptions"),
|
||||
links: this.props.groups.filter(g => g.sids.length > 0).map((g, i) => {
|
||||
links: this.props.groups.filter(g => g.sids.length > 0).map(g => {
|
||||
if (g.isMultiple) {
|
||||
let sources = g.sids.map(sid => this.props.sources[sid])
|
||||
return {
|
||||
name: g.name,
|
||||
ariaLabel: this.countOverflow(sources.map(s => s.unreadCount).reduce((a, b) => a + b, 0)),
|
||||
key: "g-" + i,
|
||||
key: "g-" + g.index,
|
||||
url: null,
|
||||
isExpanded: g.expanded,
|
||||
onClick: () => this.props.selectSourceGroup(g, "g-" + i),
|
||||
onClick: () => this.props.selectSourceGroup(g, "g-" + g.index),
|
||||
links: sources.map(this.getSource)
|
||||
}
|
||||
} else {
|
||||
|
@ -4,6 +4,7 @@ import { Icon } from "@fluentui/react/lib/Icon"
|
||||
import { AppState } from "../scripts/models/app"
|
||||
import { ProgressIndicator, IObjectWithKey } from "@fluentui/react"
|
||||
import { getWindowBreakpoint } from "../scripts/utils"
|
||||
import { WindowStateListenerType } from "../schema-types"
|
||||
|
||||
type NavProps = {
|
||||
state: AppState
|
||||
@ -24,14 +25,27 @@ type NavState = {
|
||||
class Nav extends React.Component<NavProps, NavState> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
window.utils.addWindowStateListener(this.setMaximizeState)
|
||||
this.setBodyFocusState(window.utils.isFocused())
|
||||
window.utils.addWindowStateListener(this.windowStateListener)
|
||||
this.state = {
|
||||
maximized: window.utils.isMaximized()
|
||||
}
|
||||
}
|
||||
|
||||
setMaximizeState = (state: boolean) => {
|
||||
this.setState({ maximized: state })
|
||||
setBodyFocusState = (focused: boolean) => {
|
||||
if (focused) document.body.classList.remove("blur")
|
||||
else document.body.classList.add("blur")
|
||||
}
|
||||
|
||||
windowStateListener = (type: WindowStateListenerType, state: boolean) => {
|
||||
switch (type) {
|
||||
case WindowStateListenerType.Maximized:
|
||||
this.setState({ maximized: state })
|
||||
break
|
||||
case WindowStateListenerType.Focused:
|
||||
this.setBodyFocusState(state)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
navShortcutsHandler = (e: KeyboardEvent | IObjectWithKey) => {
|
||||
@ -86,9 +100,13 @@ class Nav extends React.Component<NavProps, NavState> {
|
||||
canFetch = () => this.props.state.sourceInit && this.props.state.feedInit
|
||||
&& !this.props.state.syncing && !this.props.state.fetchingItems
|
||||
fetching = () => !this.canFetch() ? " fetching" : ""
|
||||
menuOn = () => this.props.state.menu ? " menu-on" : ""
|
||||
itemOn = () => this.props.itemShown ? " item-on" : ""
|
||||
hideButtons = () => this.props.state.settings.display ? "hide-btns" : ""
|
||||
getClassNames = () => {
|
||||
const classNames = new Array<string>()
|
||||
if (this.props.state.settings.display) classNames.push("hide-btns")
|
||||
if (this.props.state.menu) classNames.push("menu-on")
|
||||
if (this.props.itemShown) classNames.push("item-on")
|
||||
return classNames.join(" ")
|
||||
}
|
||||
|
||||
fetch = () => {
|
||||
if (this.canFetch()) this.props.fetch()
|
||||
@ -108,7 +126,7 @@ class Nav extends React.Component<NavProps, NavState> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<nav className={this.hideButtons() + this.menuOn() + this.itemOn()}>
|
||||
<nav className={this.getClassNames()}>
|
||||
<div className="btn-group">
|
||||
<a className="btn hide-wide"
|
||||
title={intl.get("nav.menu")}
|
||||
|
@ -23,8 +23,8 @@ class Settings extends React.Component<SettingsProps> {
|
||||
super(props)
|
||||
}
|
||||
|
||||
componentDidUpdate= (prevProps: SettingsProps) => {
|
||||
if (this.props.display !== prevProps.display) {
|
||||
componentDidUpdate = (prevProps: SettingsProps) => {
|
||||
if (window.utils.platform === "darwin" && this.props.display !== prevProps.display) {
|
||||
if (this.props.display) window.utils.destroyTouchBar()
|
||||
else initTouchBarWithTexts()
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import * as React from "react"
|
||||
import intl from "react-intl-universal"
|
||||
import { SourceState, RSSSource } from "../../scripts/models/source"
|
||||
import { Stack, Label, Dropdown, IDropdownOption, TextField, PrimaryButton, Icon, DropdownMenuItemType,
|
||||
DefaultButton, DetailsList, IColumn, CommandBar, ICommandBarItemProps, Selection, SelectionMode, MarqueeSelection, IDragDropEvents, Link } from "@fluentui/react"
|
||||
DefaultButton, DetailsList, IColumn, CommandBar, ICommandBarItemProps, Selection, SelectionMode, MarqueeSelection, IDragDropEvents, Link, IIconProps } from "@fluentui/react"
|
||||
import { SourceRule, RuleActions } from "../../scripts/models/rule"
|
||||
import { FilterType } from "../../scripts/models/feed"
|
||||
import { validateRegex } from "../../scripts/utils"
|
||||
@ -31,6 +31,7 @@ type RulesTabState = {
|
||||
editIndex: number
|
||||
regex: string
|
||||
fullSearch: boolean
|
||||
caseSensitive: boolean
|
||||
match: boolean
|
||||
actionKeys: string[]
|
||||
mockTitle: string
|
||||
@ -52,6 +53,7 @@ class RulesTab extends React.Component<RulesTabProps, RulesTabState> {
|
||||
editIndex: -1,
|
||||
regex: "",
|
||||
fullSearch: false,
|
||||
caseSensitive: false,
|
||||
match: true,
|
||||
actionKeys: [],
|
||||
mockTitle: "",
|
||||
@ -104,6 +106,7 @@ class RulesTab extends React.Component<RulesTabProps, RulesTabState> {
|
||||
this.setState({
|
||||
regex: rule ? rule.filter.search : "",
|
||||
fullSearch: rule ? Boolean(rule.filter.type & FilterType.FullSearch) : false,
|
||||
caseSensitive: rule ? !(rule.filter.type & FilterType.CaseInsensitive) : false,
|
||||
match: rule ? rule.match : true,
|
||||
actionKeys: rule ? RuleActions.toKeys(rule.actions) : []
|
||||
})
|
||||
@ -204,7 +207,7 @@ class RulesTab extends React.Component<RulesTabProps, RulesTabState> {
|
||||
}
|
||||
|
||||
saveRule = () => {
|
||||
let rule = new SourceRule(this.state.regex, this.state.actionKeys, this.state.fullSearch, this.state.match)
|
||||
let rule = new SourceRule(this.state.regex, this.state.actionKeys, this.state.fullSearch, this.state.caseSensitive, this.state.match)
|
||||
let source = this.props.sources[parseInt(this.state.sid)]
|
||||
let rules = source.rules ? [ ...source.rules ] : []
|
||||
if (this.state.editIndex === -1) {
|
||||
@ -269,6 +272,23 @@ class RulesTab extends React.Component<RulesTabProps, RulesTabState> {
|
||||
this.setState({ mockResult: result.join(", ") })
|
||||
}
|
||||
|
||||
toggleCaseSensitivity = () => {
|
||||
this.setState({ caseSensitive: !this.state.caseSensitive })
|
||||
}
|
||||
regexCaseIconProps = (): IIconProps => ({
|
||||
title: intl.get("context.caseSensitive"),
|
||||
children: "Aa",
|
||||
style: {
|
||||
fontSize: 12,
|
||||
fontStyle: "normal",
|
||||
cursor: "pointer",
|
||||
pointerEvents: "unset",
|
||||
color: this.state.caseSensitive ? "var(--black)" : "var(--neutralTertiary)",
|
||||
textDecoration: this.state.caseSensitive ? "underline" : "",
|
||||
},
|
||||
onClick: this.toggleCaseSensitivity
|
||||
})
|
||||
|
||||
render = () => (
|
||||
<div className="tab-body">
|
||||
<Stack horizontal tokens={{childrenGap: 16}}>
|
||||
@ -314,6 +334,7 @@ class RulesTab extends React.Component<RulesTabProps, RulesTabState> {
|
||||
<TextField
|
||||
name="regex"
|
||||
placeholder={intl.get("rules.regex")}
|
||||
iconProps={this.regexCaseIconProps()}
|
||||
value={this.state.regex}
|
||||
onGetErrorMessage={this.validateRegexField}
|
||||
validateOnLoad={false}
|
||||
|
@ -16,17 +16,17 @@ interface FeedContainerProps {
|
||||
const getSources = (state: RootState) => state.sources
|
||||
const getItems = (state: RootState) => state.items
|
||||
const getFeed = (state: RootState, props: FeedContainerProps) => state.feeds[props.feedId]
|
||||
const getKeyword = (state: RootState) => state.page.filter.search
|
||||
const getFilter = (state: RootState) => state.page.filter
|
||||
const getView = (_, props: FeedContainerProps) => props.viewType
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
return createSelector(
|
||||
[getSources, getItems, getFeed, getView, getKeyword],
|
||||
(sources, items, feed, viewType, keyword) => ({
|
||||
[getSources, getItems, getFeed, getView, getFilter],
|
||||
(sources, items, feed, viewType, filter) => ({
|
||||
feed: feed,
|
||||
items: feed.iids.map(iid => items[iid]),
|
||||
sourceMap: sources,
|
||||
keyword: keyword,
|
||||
filter: filter,
|
||||
viewType: viewType
|
||||
})
|
||||
)
|
||||
|
@ -23,7 +23,7 @@ const mapStateToProps = createSelector(
|
||||
display: app.menu,
|
||||
selected: app.menuKey,
|
||||
sources: sources,
|
||||
groups: groups,
|
||||
groups: groups.map((g, i) => ({ ...g, index: i })),
|
||||
searchOn: searchOn,
|
||||
itemOn: itemOn,
|
||||
})
|
||||
@ -47,7 +47,7 @@ const mapDispatchToProps = dispatch => ({
|
||||
dispatch(openGroupMenu(sids, event))
|
||||
},
|
||||
updateGroupExpansion: (event: React.MouseEvent<HTMLElement>, key: string, selected: string) => {
|
||||
if ((event.target as HTMLElement).tagName !== "DIV" || key === selected) {
|
||||
if ((event.target as HTMLElement).tagName === "I" || key === selected) {
|
||||
let [type, index] = key.split("-")
|
||||
if (type === "g") dispatch(toggleGroupExpansion(parseInt(index)))
|
||||
}
|
||||
|
@ -145,3 +145,11 @@ ipcMain.on("get-service-configs", (event) => {
|
||||
ipcMain.handle("set-service-configs", (_, configs: ServiceConfigs) => {
|
||||
store.set(SERVICE_CONFIGS_STORE_KEY, configs)
|
||||
})
|
||||
|
||||
const FILTER_TYPE_STORE_KEY = "filterType"
|
||||
ipcMain.on("get-filter-type", (event) => {
|
||||
event.returnValue = store.get(FILTER_TYPE_STORE_KEY, null)
|
||||
})
|
||||
ipcMain.handle("set-filter-type", (_, filterType: number) => {
|
||||
store.set(FILTER_TYPE_STORE_KEY, filterType)
|
||||
})
|
||||
|
@ -60,6 +60,7 @@ export class WindowManager {
|
||||
webviewTag: true,
|
||||
enableRemoteModule: false,
|
||||
contextIsolation: true,
|
||||
worldSafeExecuteJavaScript: true,
|
||||
spellcheck: false,
|
||||
preload: path.join(app.getAppPath(), (app.isPackaged ? "dist/" : "") + "preload.js")
|
||||
}
|
||||
@ -78,6 +79,12 @@ export class WindowManager {
|
||||
this.mainWindow.on("unmaximize", () => {
|
||||
this.mainWindow.webContents.send("unmaximized")
|
||||
})
|
||||
this.mainWindow.on("focus", () => {
|
||||
this.mainWindow.webContents.send("window-focus")
|
||||
})
|
||||
this.mainWindow.on("blur", () => {
|
||||
this.mainWindow.webContents.send("window-blur")
|
||||
})
|
||||
this.mainWindow.webContents.on("context-menu", (_, params) => {
|
||||
if (params.selectionText) {
|
||||
this.mainWindow.webContents.send("window-context-menu", [params.x, params.y], params.selectionText)
|
||||
|
@ -3,7 +3,7 @@ export class SourceGroup {
|
||||
sids: number[]
|
||||
name?: string
|
||||
expanded?: boolean
|
||||
index?: number // available only from groups tab container
|
||||
index?: number // available only from menu or groups tab container
|
||||
|
||||
constructor(sids: number[], name: string = null) {
|
||||
name = (name && name.trim()) || "Source group"
|
||||
@ -44,6 +44,10 @@ export interface ServiceConfigs {
|
||||
importGroups?: boolean
|
||||
}
|
||||
|
||||
export const enum WindowStateListenerType {
|
||||
Maximized, Focused
|
||||
}
|
||||
|
||||
export interface TouchBarTexts {
|
||||
menu: string
|
||||
search: string
|
||||
@ -65,4 +69,5 @@ export type SchemaTypes = {
|
||||
fetchInterval: number
|
||||
searchEngine: SearchEngines
|
||||
serviceConfigs: ServiceConfigs
|
||||
filterType: number
|
||||
}
|
||||
|
@ -88,7 +88,8 @@
|
||||
"manageSources": "Manage sources",
|
||||
"saveImageAs": "Save image as …",
|
||||
"copyImage": "Copy image",
|
||||
"copyImageURL": "Copy image link"
|
||||
"copyImageURL": "Copy image link",
|
||||
"caseSensitive": "Case sensitive"
|
||||
},
|
||||
"searchEngine": {
|
||||
"name": "Search engine",
|
||||
|
@ -88,7 +88,8 @@
|
||||
"manageSources": "管理订阅源",
|
||||
"saveImageAs": "将图像另存为",
|
||||
"copyImage": "复制图像",
|
||||
"copyImageURL": "复制图像链接"
|
||||
"copyImageURL": "复制图像链接",
|
||||
"caseSensitive": "区分大小写"
|
||||
},
|
||||
"searchEngine": {
|
||||
"name": "搜索引擎",
|
||||
|
@ -10,17 +10,21 @@ export enum FilterType {
|
||||
ShowNotStarred = 1 << 1,
|
||||
ShowHidden = 1 << 2,
|
||||
FullSearch = 1 << 3,
|
||||
CaseInsensitive = 1 << 4,
|
||||
|
||||
Default = ShowRead | ShowNotStarred,
|
||||
UnreadOnly = ShowNotStarred,
|
||||
StarredOnly = ShowRead,
|
||||
Toggles = ShowHidden | FullSearch
|
||||
Toggles = ShowHidden | FullSearch | CaseInsensitive,
|
||||
}
|
||||
export class FeedFilter {
|
||||
type: FilterType
|
||||
search: string
|
||||
|
||||
constructor(type=FilterType.Default, search="") {
|
||||
constructor(type: FilterType = null, search="") {
|
||||
if (type === null && (type = window.settings.getFilterType()) === null) {
|
||||
type = FilterType.Default | FilterType.CaseInsensitive
|
||||
}
|
||||
this.type = type
|
||||
this.search = search
|
||||
}
|
||||
@ -36,7 +40,8 @@ export class FeedFilter {
|
||||
if (type & FilterType.ShowNotStarred) delete query.starred
|
||||
if (type & FilterType.ShowHidden) delete query.hidden
|
||||
if (filter.search !== "") {
|
||||
let regex = RegExp(filter.search)
|
||||
const flags = (type & FilterType.CaseInsensitive) ? "i" : ""
|
||||
const regex = RegExp(filter.search, flags)
|
||||
if (type & FilterType.FullSearch) {
|
||||
query.$or = [
|
||||
{ title: { $regex: regex } },
|
||||
@ -56,7 +61,8 @@ export class FeedFilter {
|
||||
if (!(type & FilterType.ShowNotStarred)) flag = flag && item.starred
|
||||
if (!(type & FilterType.ShowHidden)) flag = flag && !item.hidden
|
||||
if (filter.search !== "") {
|
||||
let regex = RegExp(filter.search)
|
||||
const flags = (type & FilterType.CaseInsensitive) ? "i" : ""
|
||||
const regex = RegExp(filter.search, flags)
|
||||
if (type & FilterType.FullSearch) {
|
||||
flag = flag && (regex.test(item.title) || regex.test(item.snippet))
|
||||
} else {
|
||||
|
@ -174,7 +174,9 @@ const applyFilterDone = (filter: FeedFilter): PageActionTypes => ({
|
||||
})
|
||||
|
||||
function applyFilter(filter: FeedFilter): AppThunk {
|
||||
return (dispatch) => {
|
||||
return (dispatch, getState) => {
|
||||
const oldFilterType = getState().page.filter.type
|
||||
if (filter.type !== oldFilterType) window.settings.setFilterType(filter.type)
|
||||
dispatch(applyFilterDone(filter))
|
||||
dispatch(initFeeds(true))
|
||||
}
|
||||
|
@ -65,9 +65,10 @@ export class SourceRule {
|
||||
match: boolean
|
||||
actions: RuleActions
|
||||
|
||||
constructor(regex: string, actions: string[], fullSearch: boolean, match: boolean) {
|
||||
constructor(regex: string, actions: string[], fullSearch: boolean, caseSensitive: boolean, match: boolean) {
|
||||
this.filter = new FeedFilter(FilterType.Default | FilterType.ShowHidden, regex)
|
||||
if (fullSearch) this.filter.type |= FilterType.FullSearch
|
||||
if (!caseSensitive) this.filter.type |= FilterType.CaseInsensitive
|
||||
this.match = match
|
||||
this.actions = RuleActions.fromKeys(actions)
|
||||
}
|
||||
|
@ -29,6 +29,30 @@ const rssParser = new Parser({
|
||||
})
|
||||
|
||||
const CHARSET_RE = /charset=([^()<>@,;:\"/[\]?.=\s]*)/i
|
||||
const XML_ENCODING_RE = /^<\?xml.+encoding="(.+)".*?\?>/i
|
||||
export async function decodeFetchResponse(response: Response, isHTML = false) {
|
||||
const buffer = await response.arrayBuffer()
|
||||
let ctype = response.headers.has("content-type") && response.headers.get("content-type")
|
||||
let charset = (ctype && CHARSET_RE.test(ctype)) ? CHARSET_RE.exec(ctype)[1] : undefined
|
||||
let content = (new TextDecoder(charset)).decode(buffer)
|
||||
if (charset === undefined) {
|
||||
if (isHTML) {
|
||||
const dom = domParser.parseFromString(content, "text/html")
|
||||
charset = dom.querySelector("meta[charset]")?.getAttribute("charset")?.toLowerCase()
|
||||
if (!charset) {
|
||||
ctype = dom.querySelector("meta[http-equiv='Content-Type']")?.getAttribute("content")
|
||||
charset = ctype && CHARSET_RE.test(ctype) && CHARSET_RE.exec(ctype)[1].toLowerCase()
|
||||
}
|
||||
} else {
|
||||
charset = XML_ENCODING_RE.test(content) && XML_ENCODING_RE.exec(content)[1].toLowerCase()
|
||||
}
|
||||
if (charset && charset !== "utf-8" && charset !== "utf8") {
|
||||
content = (new TextDecoder(charset)).decode(buffer)
|
||||
}
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
export async function parseRSS(url: string) {
|
||||
let result: Response
|
||||
try {
|
||||
@ -38,11 +62,7 @@ export async function parseRSS(url: string) {
|
||||
}
|
||||
if (result && result.ok) {
|
||||
try {
|
||||
const buffer = await result.arrayBuffer()
|
||||
const ctype = result.headers.has("content-type") && result.headers.get("content-type")
|
||||
const charset = (ctype && CHARSET_RE.test(ctype)) ? CHARSET_RE.exec(ctype)[1] : "utf-8"
|
||||
const decoder = new TextDecoder(charset)
|
||||
return await rssParser.parseString(decoder.decode(buffer))
|
||||
return await rssParser.parseString(await decodeFetchResponse(result))
|
||||
} catch {
|
||||
throw new Error(intl.get("log.parseError"))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user