Add notifications for new blog posts and app updates
This commit is contained in:
parent
c36a9d9254
commit
b0d1ddf1ac
|
@ -3186,8 +3186,7 @@
|
|||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
|
@ -13583,6 +13582,24 @@
|
|||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"markdown": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown/-/markdown-0.5.0.tgz",
|
||||
"integrity": "sha1-KCBbVlqK51kt4gdGPWY33BgnIrI=",
|
||||
"requires": {
|
||||
"nopt": "~2.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"nopt": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz",
|
||||
"integrity": "sha1-bMzZd7gBMqB3MdbozljCyDA8+a8=",
|
||||
"requires": {
|
||||
"abbrev": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"matcher": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.uniqwith": "^4.5.0",
|
||||
"markdown": "^0.5.0",
|
||||
"material-design-icons": "^3.0.1",
|
||||
"mediaelement": "^4.2.16",
|
||||
"nedb": "^1.8.0",
|
||||
|
|
|
@ -24,12 +24,16 @@ body {
|
|||
}
|
||||
|
||||
.banner {
|
||||
margin-top: 70px;
|
||||
margin-bottom: -65px;
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
.flexBox {
|
||||
margin-top: 60px;
|
||||
margin-bottom: -75px;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .2s;
|
||||
transition: opacity .15s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||
opacity: 0;
|
||||
|
@ -42,7 +46,11 @@ body {
|
|||
}
|
||||
|
||||
.banner {
|
||||
margin-top: 70px;
|
||||
margin-bottom: -65px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.flexBox {
|
||||
margin-top: 60px;
|
||||
margin-bottom: -75px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import Vue from 'vue'
|
||||
import { ObserveVisibility } from 'vue-observe-visibility'
|
||||
import FtFlexBox from './components/ft-flex-box/ft-flex-box.vue'
|
||||
import TopNav from './components/top-nav/top-nav.vue'
|
||||
import SideNav from './components/side-nav/side-nav.vue'
|
||||
import FtNotificationBanner from './components/ft-notification-banner/ft-notification-banner.vue'
|
||||
import FtPrompt from './components/ft-prompt/ft-prompt.vue'
|
||||
import FtButton from './components/ft-button/ft-button.vue'
|
||||
import FtToast from './components/ft-toast/ft-toast.vue'
|
||||
import FtProgressBar from './components/ft-progress-bar/ft-progress-bar.vue'
|
||||
import $ from 'jquery'
|
||||
import { markdown } from 'markdown'
|
||||
import Parser from 'rss-parser'
|
||||
|
||||
let useElectron
|
||||
let shell
|
||||
|
@ -22,14 +28,26 @@ if (window && window.process && window.process.type === 'renderer') {
|
|||
export default Vue.extend({
|
||||
name: 'App',
|
||||
components: {
|
||||
FtFlexBox,
|
||||
TopNav,
|
||||
SideNav,
|
||||
FtNotificationBanner,
|
||||
FtPrompt,
|
||||
FtButton,
|
||||
FtToast,
|
||||
FtProgressBar
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
hideOutlines: true
|
||||
hideOutlines: true,
|
||||
showUpdatesBanner: false,
|
||||
showBlogBanner: false,
|
||||
showReleaseNotes: false,
|
||||
updateBannerMessage: '',
|
||||
blogBannerMessage: '',
|
||||
latestBlogUrl: '',
|
||||
updateChangelog: '',
|
||||
changeLogTitle: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -41,6 +59,12 @@ export default Vue.extend({
|
|||
},
|
||||
isRightAligned: function () {
|
||||
return this.$i18n.locale === 'ar'
|
||||
},
|
||||
checkForUpdates: function () {
|
||||
return this.$store.getters.getCheckForUpdates
|
||||
},
|
||||
checkForBlogPosts: function () {
|
||||
return this.$store.getters.getCheckForBlogPosts
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
|
@ -56,6 +80,11 @@ export default Vue.extend({
|
|||
this.activateKeyboardShortcuts()
|
||||
this.openAllLinksExternally()
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.checkForNewUpdates()
|
||||
this.checkForNewBlogPosts()
|
||||
}, 500)
|
||||
},
|
||||
methods: {
|
||||
checkLocale: function () {
|
||||
|
@ -105,6 +134,80 @@ export default Vue.extend({
|
|||
localStorage.setItem('secColor', theme.secColor)
|
||||
},
|
||||
|
||||
checkForNewUpdates: function () {
|
||||
if (this.checkForUpdates) {
|
||||
const { version } = require('../../package.json')
|
||||
const requestUrl = 'https://api.github.com/repos/freetubeapp/freetube-vue/releases'
|
||||
|
||||
$.getJSON(requestUrl, (response) => {
|
||||
const tagName = response[0].tag_name
|
||||
const versionNumber = tagName.replace('v', '').replace('-beta', '')
|
||||
this.updateChangelog = markdown.toHTML(response[0].body)
|
||||
this.changeLogTitle = response[0].name
|
||||
|
||||
const message = this.$t('Version $ is now available! Click for more details')
|
||||
this.updateBannerMessage = message.replace('$', versionNumber)
|
||||
if (version < versionNumber) {
|
||||
this.showUpdatesBanner = true
|
||||
}
|
||||
}).fail((xhr, textStatus, error) => {
|
||||
console.log(xhr)
|
||||
console.log(textStatus)
|
||||
console.log(requestUrl)
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
checkForNewBlogPosts: function () {
|
||||
if (this.checkForBlogPosts) {
|
||||
const parser = new Parser()
|
||||
const feedUrl = 'https://write.as/freetube/feed/'
|
||||
let lastAppWasRunning = localStorage.getItem('lastAppWasRunning')
|
||||
|
||||
if (lastAppWasRunning !== null) {
|
||||
lastAppWasRunning = new Date(lastAppWasRunning)
|
||||
}
|
||||
|
||||
parser.parseURL(feedUrl).then((response) => {
|
||||
const latestBlog = response.items[0]
|
||||
const latestPubDate = new Date(latestBlog.pubDate)
|
||||
|
||||
if (lastAppWasRunning === null || latestPubDate > lastAppWasRunning) {
|
||||
const message = this.$t('A new blog is now available, $. Click to view more')
|
||||
this.blogBannerMessage = message.replace('$', latestBlog.title)
|
||||
this.latestBlogUrl = latestBlog.link
|
||||
this.showBlogBanner = true
|
||||
}
|
||||
|
||||
localStorage.setItem('lastAppWasRunning', new Date())
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
handleUpdateBannerClick: function (response) {
|
||||
if (response !== false) {
|
||||
this.showReleaseNotes = true
|
||||
} else {
|
||||
this.showUpdatesBanner = false
|
||||
}
|
||||
},
|
||||
|
||||
handleNewBlogBannerClick: function (response) {
|
||||
if (response) {
|
||||
shell.openExternal(this.latestBlogUrl)
|
||||
}
|
||||
|
||||
this.showBlogBanner = false
|
||||
},
|
||||
|
||||
openDownloadsPage: function () {
|
||||
const url = 'https://freetubeapp.io#download'
|
||||
shell.openExternal(url)
|
||||
this.showReleaseNotes = false
|
||||
this.showUpdatesBanner = false
|
||||
},
|
||||
|
||||
activateKeyboardShortcuts: function () {
|
||||
$(document).on('keydown', this.handleKeyboardShortcuts)
|
||||
$(document).on('mousedown', () => {
|
||||
|
|
|
@ -8,6 +8,24 @@
|
|||
>
|
||||
<top-nav ref="topNav" />
|
||||
<side-nav ref="sideNav" />
|
||||
<ft-flex-box
|
||||
v-if="showUpdatesBanner || showBlogBanner"
|
||||
class="flexBox routerView"
|
||||
:class="{ expand: !isOpen }"
|
||||
>
|
||||
<ft-notification-banner
|
||||
v-if="showUpdatesBanner"
|
||||
class="banner"
|
||||
:message="updateBannerMessage"
|
||||
@click="handleUpdateBannerClick"
|
||||
/>
|
||||
<ft-notification-banner
|
||||
v-if="showBlogBanner"
|
||||
class="banner"
|
||||
:message="blogBannerMessage"
|
||||
@click="handleNewBlogBannerClick"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
<transition
|
||||
mode="out-in"
|
||||
name="fade"
|
||||
|
@ -20,6 +38,21 @@
|
|||
/>
|
||||
<!-- </keep-alive> -->
|
||||
</transition>
|
||||
<ft-prompt
|
||||
v-if="showReleaseNotes"
|
||||
@click="showReleaseNotes = !showReleaseNotes"
|
||||
>
|
||||
<h2>
|
||||
{{ changeLogTitle }}
|
||||
</h2>
|
||||
<span v-html="updateChangelog" />
|
||||
<ft-flex-box>
|
||||
<ft-button
|
||||
:label="$t('Download From Site')"
|
||||
@click="openDownloadsPage"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
</ft-prompt>
|
||||
<ft-toast />
|
||||
<ft-progress-bar
|
||||
v-if="showProgressBar"
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
.ftNotificationBanner {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--text-with-main-color);
|
||||
/*
|
||||
background-color: var(--accent-color);
|
||||
color: var(--text-with-accent-color);
|
||||
*/
|
||||
margin: 4px;
|
||||
padding: 16px;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 5px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.1);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-right: 25px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bannerIcon {
|
||||
position: absolute;
|
||||
top: 35%;
|
||||
right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'FtNotificationBanner',
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
progressBarPercentage: function () {
|
||||
return this.$store.getters.getProgressBarPercentage
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick: function (response) {
|
||||
this.$emit('click', response)
|
||||
},
|
||||
|
||||
handleClose: function (event) {
|
||||
event.stopPropagation()
|
||||
this.handleClick(false)
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<div
|
||||
class="ftNotificationBanner"
|
||||
@click="handleClick(true)"
|
||||
>
|
||||
<div
|
||||
class="message"
|
||||
>
|
||||
<slot>
|
||||
<p>
|
||||
{{ message }}
|
||||
</p>
|
||||
</slot>
|
||||
</div>
|
||||
<font-awesome-icon
|
||||
class="bannerIcon"
|
||||
icon="times"
|
||||
@click="handleClose"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./ft-notification-banner.js" />
|
||||
<style scoped src="./ft-notification-banner.css" />
|
|
@ -11,7 +11,11 @@
|
|||
|
||||
.promptCard {
|
||||
width: 95%;
|
||||
margin-top: 40vh;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
-ms-transform: translateY(-40%);
|
||||
transform: translateY(-40%);
|
||||
}
|
||||
|
||||
.center {
|
||||
|
|
|
@ -547,6 +547,9 @@ export default Vue.extend({
|
|||
checkForUpdates: function () {
|
||||
return this.$store.getters.getCheckForUpdates
|
||||
},
|
||||
checkForBlogPosts: function () {
|
||||
return this.$store.getters.getCheckForBlogPosts
|
||||
},
|
||||
backendPreference: function () {
|
||||
return this.$store.getters.getBackendPreference
|
||||
},
|
||||
|
@ -658,6 +661,7 @@ export default Vue.extend({
|
|||
'updateEnableSearchSuggestions',
|
||||
'updateBackendFallback',
|
||||
'updateCheckForUpdates',
|
||||
'updateCheckForBlogPosts',
|
||||
'updateBarColor',
|
||||
'updateBackendPreference',
|
||||
'updateLandingPage',
|
||||
|
|
|
@ -7,24 +7,36 @@
|
|||
>
|
||||
{{ $t("Settings.General Settings.General Settings") }}
|
||||
</h3>
|
||||
<ft-flex-box class="generalSettingsFlexBox">
|
||||
<div class="switchColumnGrid">
|
||||
<div class="switchColumn">
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.General Settings.Check for Updates')"
|
||||
:default-value="checkForUpdates"
|
||||
:compact="true"
|
||||
@change="updateCheckForUpdates"
|
||||
/>
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.General Settings.Fallback to Non-Preferred Backend on Failure')"
|
||||
:default-value="backendFallback"
|
||||
:compact="true"
|
||||
@change="updateBackendFallback"
|
||||
/>
|
||||
</div>
|
||||
<div class="switchColumn">
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.General Settings.Check for Latest Blog Posts')"
|
||||
:default-value="checkForBlogPosts"
|
||||
:compact="true"
|
||||
@change="updateCheckForBlogPosts"
|
||||
/>
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.General Settings.Enable Search Suggestions')"
|
||||
:default-value="enableSearchSuggestions"
|
||||
:compact="true"
|
||||
@change="updateEnableSearchSuggestions"
|
||||
/>
|
||||
<ft-toggle-switch
|
||||
v-if="false"
|
||||
:label="$t('Settings.General Settings.Check for Updates')"
|
||||
:default-value="checkForUpdates"
|
||||
@change="updateCheckForUpdates"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
</div>
|
||||
</div>
|
||||
<div class="switchGrid">
|
||||
<ft-select
|
||||
:placeholder="$t('Settings.General Settings.Preferred API Backend.Preferred API Backend')"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue'
|
||||
import FtListDropdown from '../ft-list-dropdown/ft-list-dropdown.vue'
|
||||
// import { shell } from 'electron'
|
||||
import { shell } from 'electron'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'FtElementList',
|
||||
|
@ -101,13 +101,13 @@ export default Vue.extend({
|
|||
navigator.clipboard.writeText(youtubeUrl)
|
||||
break
|
||||
case 'openYoutube':
|
||||
// shell.openExternal(youtubeUrl)
|
||||
shell.openExternal(youtubeUrl)
|
||||
break
|
||||
case 'copyInvidious':
|
||||
navigator.clipboard.writeText(invidiousUrl)
|
||||
break
|
||||
case 'openInvidious':
|
||||
// shell.openExternal(invidiousUrl)
|
||||
shell.openExternal(invidiousUrl)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ export default Vue.extend({
|
|||
}
|
||||
})
|
||||
|
||||
this.debounceSearchResults = debounce(this.getSearchSuggestions, 500)
|
||||
this.debounceSearchResults = debounce(this.getSearchSuggestions, 200)
|
||||
},
|
||||
methods: {
|
||||
goToSearch: function (query) {
|
||||
|
|
|
@ -30,6 +30,7 @@ const state = {
|
|||
currentTheme: 'lightRed',
|
||||
backendFallback: true,
|
||||
checkForUpdates: true,
|
||||
checkForBlogPosts: true,
|
||||
backendPreference: 'local',
|
||||
landingPage: 'subscriptions',
|
||||
region: 'US',
|
||||
|
@ -70,6 +71,10 @@ const getters = {
|
|||
return state.checkForUpdates
|
||||
},
|
||||
|
||||
getCheckForBlogPosts: () => {
|
||||
return state.checkForBlogPosts
|
||||
},
|
||||
|
||||
getBarColor: () => {
|
||||
return state.barColor
|
||||
},
|
||||
|
@ -194,6 +199,9 @@ const actions = {
|
|||
case 'checkForUpdates':
|
||||
commit('setCheckForUpdates', result.value)
|
||||
break
|
||||
case 'checkForBlogPosts':
|
||||
commit('setCheckForBlogPosts', result.value)
|
||||
break
|
||||
case 'enableSearchSuggestions':
|
||||
commit('setEnableSearchSuggestions', result.value)
|
||||
break
|
||||
|
@ -300,6 +308,14 @@ const actions = {
|
|||
})
|
||||
},
|
||||
|
||||
updateCheckForBlogPosts ({ commit }, checkForBlogPosts) {
|
||||
settingsDb.update({ _id: 'checkForBlogPosts' }, { _id: 'checkForBlogPosts', value: checkForBlogPosts }, { upsert: true }, (err, numReplaced) => {
|
||||
if (!err) {
|
||||
commit('setCheckForBlogPosts', checkForBlogPosts)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
updateEnableSearchSuggestions ({ commit }, enableSearchSuggestions) {
|
||||
settingsDb.update({ _id: 'enableSearchSuggestions' }, { _id: 'enableSearchSuggestions', value: enableSearchSuggestions }, { upsert: true }, (err, numReplaced) => {
|
||||
if (!err) {
|
||||
|
@ -502,6 +518,9 @@ const mutations = {
|
|||
setCheckForUpdates (state, checkForUpdates) {
|
||||
state.checkForUpdates = checkForUpdates
|
||||
},
|
||||
setCheckForBlogPosts (state, checkForBlogPosts) {
|
||||
state.checkForBlogPosts = checkForBlogPosts
|
||||
},
|
||||
setBackendPreference (state, backendPreference) {
|
||||
state.backendPreference = backendPreference
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@ const state = {
|
|||
trendingCache: null,
|
||||
showProgressBar: false,
|
||||
progressBarPercentage: 0,
|
||||
recentBlogPosts: [],
|
||||
searchSettings: {
|
||||
sortBy: 'relevance',
|
||||
time: '',
|
||||
|
@ -86,6 +87,10 @@ const getters = {
|
|||
|
||||
getProgressBarPercentage () {
|
||||
return state.progressBarPercentage
|
||||
},
|
||||
|
||||
getRecentBlogPosts () {
|
||||
return state.recentBlogPosts
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -399,6 +404,10 @@ const mutations = {
|
|||
|
||||
setSearchDuration (state, value) {
|
||||
state.searchSettings.duration = value
|
||||
},
|
||||
|
||||
setRecentBlogPosts (state, value) {
|
||||
state.recentBlogPosts = value
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,10 @@ Close: Close
|
|||
Back: Back
|
||||
Forward: Forward
|
||||
|
||||
Version $ is now available! Click for more details: Version $ is now available! Click for more details
|
||||
Download From Site: Download From Site
|
||||
A new blog is now available, $. Click to view more: A new blog is now available, $. Click to view more
|
||||
|
||||
# Search Bar
|
||||
Search / Go to URL: Search / Go to URL
|
||||
# In Filter Button
|
||||
|
@ -87,6 +91,8 @@ Settings:
|
|||
Settings: Settings
|
||||
General Settings:
|
||||
General Settings: General Settings
|
||||
Check for Updates: Check for Updates
|
||||
Check for Latest Blog Posts: Check for Latest Blog Posts
|
||||
Fallback to Non-Preferred Backend on Failure: Fallback to Non-Preferred Backend
|
||||
on Failure
|
||||
Enable Search Suggestions: Enable Search Suggestions
|
||||
|
|
Loading…
Reference in New Issue