mirror of
https://github.com/nkanaev/yarr.git
synced 2026-07-15 11:06:31 +00:00
723 lines
23 KiB
TypeScript
723 lines
23 KiB
TypeScript
import Vue from 'vue/dist/vue.esm.js'
|
|
import i18n from './i18n'
|
|
import api from './api'
|
|
import template from './templates/app.html' with {type: 'text'}
|
|
import icons from './icons'
|
|
import { setupKeybindings } from './key'
|
|
import { scrollto, debounce, dateRepr } from './utils'
|
|
import drag from './components/drag'
|
|
import dropdown from './components/dropdown'
|
|
import modal from './components/modal'
|
|
import relativeTime from './components/relative-time'
|
|
import icon from './components/icon'
|
|
import scrollDir from './directives/scroll'
|
|
import focusDir from './directives/focus'
|
|
|
|
var app = window.app
|
|
var vm
|
|
|
|
var TITLE = document.title
|
|
|
|
export default {
|
|
template: template,
|
|
components: {
|
|
'v-drag': drag,
|
|
'v-dropdown': dropdown,
|
|
'v-modal': modal,
|
|
'v-relative-time': relativeTime,
|
|
'v-icon': icon,
|
|
},
|
|
directives: {
|
|
scroll: scrollDir,
|
|
focus: focusDir,
|
|
},
|
|
created: function() {
|
|
vm = this
|
|
this.refreshStats()
|
|
.then(this.refreshFeeds.bind(this))
|
|
.then(this.refreshItems.bind(this, false))
|
|
|
|
api.feeds.list_errors().then(function(errors) {
|
|
vm.feed_errors = errors
|
|
})
|
|
this.updateMetaTheme(app.settings.theme_name)
|
|
this.$setLang(app.settings.language)
|
|
|
|
// keep the theme-color meta tag in sync when the OS color scheme changes
|
|
if (window.matchMedia) {
|
|
this._colorSchemeMql = window.matchMedia('(prefers-color-scheme: dark)')
|
|
this._colorSchemeHandler = function() {
|
|
this.updateMetaTheme(this.theme.name)
|
|
}.bind(this)
|
|
this._colorSchemeMql.addEventListener('change', this._colorSchemeHandler)
|
|
}
|
|
},
|
|
beforeDestroy: function() {
|
|
if (this._colorSchemeMql) {
|
|
this._colorSchemeMql.removeEventListener('change', this._colorSchemeHandler)
|
|
}
|
|
},
|
|
mounted: function() {
|
|
setupKeybindings(this)
|
|
},
|
|
data: function() {
|
|
var s = app.settings
|
|
return {
|
|
'filterSelected': s.filter,
|
|
'folders': [],
|
|
'feeds': [],
|
|
'feedSelected': s.feed,
|
|
'feedListWidth': s.feed_list_width || 300,
|
|
'feedNewChoice': [],
|
|
'feedNewChoiceSelected': '',
|
|
'items': [],
|
|
'itemsHasMore': true,
|
|
'itemSelected': null,
|
|
'itemSelectedDetails': null,
|
|
'itemSelectedReadability': '',
|
|
'itemSearch': '',
|
|
'itemSortNewestFirst': s.sort_newest_first,
|
|
'itemListWidth': s.item_list_width || 300,
|
|
|
|
'filteredFeedStats': {},
|
|
'filteredFolderStats': {},
|
|
'filteredTotalStats': null,
|
|
|
|
'settings': '',
|
|
'loading': {
|
|
'feeds': 0,
|
|
'newfeed': false,
|
|
'items': false,
|
|
'readability': false,
|
|
},
|
|
'fonts': ['', 'serif', 'monospace'],
|
|
'feedStats': {},
|
|
'theme': {
|
|
'name': s.theme_name,
|
|
'font': s.theme_font,
|
|
'size': s.theme_size,
|
|
},
|
|
'themeColors': {
|
|
'night': '#0e0e0e',
|
|
'sepia': '#f4f0e5',
|
|
'light': '#fff',
|
|
},
|
|
'refreshRate': s.refresh_rate,
|
|
'authenticated': app.authenticated,
|
|
'requiresAuth': app.requiresAuth,
|
|
'feed_errors': {},
|
|
|
|
'refreshRateOptions': [
|
|
{ title: "0", value: 0 },
|
|
{ title: "10m", value: 10 },
|
|
{ title: "30m", value: 30 },
|
|
{ title: "1h", value: 60 },
|
|
{ title: "2h", value: 120 },
|
|
{ title: "4h", value: 240 },
|
|
{ title: "12h", value: 720 },
|
|
{ title: "24h", value: 1440 },
|
|
],
|
|
|
|
'language': s.language,
|
|
'languages': [
|
|
{code: 'en', name: 'English' },
|
|
{code: 'de', name: 'Deutsch'},
|
|
{code: 'es', name: 'Español'},
|
|
{code: 'fr', name: 'Français'},
|
|
{code: 'ja', name: '日本語'},
|
|
{code: 'pt', name: 'Português'},
|
|
{code: 'ru', name: 'Русский'},
|
|
{code: 'zh', name: '简体中文'},
|
|
]
|
|
}
|
|
},
|
|
computed: {
|
|
foldersWithFeeds: function() {
|
|
var feedsByFolders = this.feeds.reduce(function(folders, feed) {
|
|
if (!folders[feed.folder_id])
|
|
folders[feed.folder_id] = [feed]
|
|
else
|
|
folders[feed.folder_id].push(feed)
|
|
return folders
|
|
}, {})
|
|
var folders = this.folders.slice().map(function(folder) {
|
|
folder.feeds = feedsByFolders[folder.id]
|
|
return folder
|
|
})
|
|
folders.push({id: null, feeds: feedsByFolders[null]})
|
|
return folders
|
|
},
|
|
feedsById: function() {
|
|
return this.feeds.reduce(function(acc, f) { acc[f.id] = f; return acc }, {})
|
|
},
|
|
foldersById: function() {
|
|
return this.folders.reduce(function(acc, f) { acc[f.id] = f; return acc }, {})
|
|
},
|
|
current: function() {
|
|
var parts = (this.feedSelected || '').split(':', 2)
|
|
var type = parts[0]
|
|
var guid = parts[1]
|
|
|
|
var folder = {}, feed = {}
|
|
|
|
if (type == 'feed')
|
|
feed = this.feedsById[guid] || {}
|
|
if (type == 'folder')
|
|
folder = this.foldersById[guid] || {}
|
|
|
|
return {type: type, feed: feed, folder: folder}
|
|
},
|
|
searchScope: function() {
|
|
void this.language
|
|
var type = (this.feedSelected || '').split(':', 2)[0]
|
|
if (type == 'feed')
|
|
return (this.feedsById[this.feedSelected.split(':', 2)[1]] || {}).title || ''
|
|
if (type == 'folder')
|
|
return (this.foldersById[this.feedSelected.split(':', 2)[1]] || {}).title || ''
|
|
if (this.filterSelected == 'unread')
|
|
return this.$t('all_unread')
|
|
if (this.filterSelected == 'starred')
|
|
return this.$t('all_starred')
|
|
return this.$t('all_feeds')
|
|
},
|
|
itemSelectedContent: function() {
|
|
if (!this.itemSelected) return ''
|
|
|
|
if (this.itemSelectedReadability)
|
|
return this.itemSelectedReadability
|
|
|
|
return this.itemSelectedDetails.content || ''
|
|
},
|
|
contentImages: function() {
|
|
if (!this.itemSelectedDetails) return []
|
|
return (this.itemSelectedDetails.media_links || []).filter(l => l.type === 'image')
|
|
},
|
|
contentAudios: function() {
|
|
if (!this.itemSelectedDetails) return []
|
|
return (this.itemSelectedDetails.media_links || []).filter(l => l.type === 'audio')
|
|
},
|
|
contentVideos: function() {
|
|
if (!this.itemSelectedDetails) return []
|
|
return (this.itemSelectedDetails.media_links || []).filter(l => l.type === 'video')
|
|
},
|
|
refreshRateTitle: function () {
|
|
const entry = this.refreshRateOptions.find(o => o.value === this.refreshRate)
|
|
return entry ? entry.title : '0'
|
|
},
|
|
},
|
|
watch: {
|
|
'theme': {
|
|
deep: true,
|
|
handler: function(theme) {
|
|
this.updateMetaTheme(theme.name)
|
|
document.body.classList.value = 'theme-' + theme.name
|
|
api.settings.update({
|
|
theme_name: theme.name,
|
|
theme_font: theme.font,
|
|
theme_size: theme.size,
|
|
})
|
|
},
|
|
},
|
|
'feedStats': {
|
|
deep: true,
|
|
handler: debounce(function() {
|
|
var title = TITLE
|
|
var unreadCount = Object.values(this.feedStats).reduce(function(acc, stat) {
|
|
return acc + stat.unread
|
|
}, 0)
|
|
if (unreadCount) {
|
|
title += ' ('+unreadCount+')'
|
|
}
|
|
document.title = title
|
|
this.computeStats()
|
|
}, 500),
|
|
},
|
|
'filterSelected': function(newVal, oldVal) {
|
|
if (oldVal === undefined) return // do nothing, initial setup
|
|
this.itemSelected = null
|
|
this.items = []
|
|
this.itemsHasMore = true
|
|
api.settings.update({filter: newVal}).then(this.refreshItems.bind(this, false))
|
|
this.computeStats()
|
|
},
|
|
'feedSelected': function(newVal, oldVal) {
|
|
if (oldVal === undefined) return // do nothing, initial setup
|
|
this.itemSelected = null
|
|
this.items = []
|
|
this.itemsHasMore = true
|
|
api.settings.update({feed: newVal}).then(this.refreshItems.bind(this, false))
|
|
if (this.$refs.itemlist) this.$refs.itemlist.scrollTop = 0
|
|
},
|
|
'itemSelected': function(newVal, oldVal) {
|
|
this.itemSelectedReadability = ''
|
|
if (newVal === null) {
|
|
this.itemSelectedDetails = null
|
|
return
|
|
}
|
|
if (this.$refs.content) this.$refs.content.scrollTop = 0
|
|
|
|
api.items.get(newVal).then(function(item) {
|
|
this.itemSelectedDetails = item
|
|
if (this.itemSelectedDetails.status == 'unread') {
|
|
api.items.update(this.itemSelectedDetails.id, {status: 'read'}).then(function() {
|
|
this.feedStats[this.itemSelectedDetails.feed_id].unread -= 1
|
|
var itemInList = this.items.find(function(i) { return i.id == item.id })
|
|
if (itemInList) itemInList.status = 'read'
|
|
this.itemSelectedDetails.status = 'read'
|
|
}.bind(this))
|
|
}
|
|
}.bind(this))
|
|
},
|
|
'itemSearch': debounce(function(newVal) {
|
|
this.refreshItems()
|
|
}, 500),
|
|
'itemSortNewestFirst': function(newVal, oldVal) {
|
|
if (oldVal === undefined) return // do nothing, initial setup
|
|
api.settings.update({sort_newest_first: newVal}).then(vm.refreshItems.bind(this, false))
|
|
},
|
|
'feedListWidth': debounce(function(newVal, oldVal) {
|
|
if (oldVal === undefined) return // do nothing, initial setup
|
|
api.settings.update({feed_list_width: newVal})
|
|
}, 1000),
|
|
'itemListWidth': debounce(function(newVal, oldVal) {
|
|
if (oldVal === undefined) return // do nothing, initial setup
|
|
api.settings.update({item_list_width: newVal})
|
|
}, 1000),
|
|
'refreshRate': function(newVal, oldVal) {
|
|
if (oldVal === undefined) return // do nothing, initial setup
|
|
api.settings.update({refresh_rate: newVal})
|
|
},
|
|
},
|
|
methods: {
|
|
updateMetaTheme: function(theme) {
|
|
if (theme == 'system') {
|
|
var dark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
theme = dark ? 'night' : 'light'
|
|
}
|
|
document.querySelector("meta[name='theme-color']").content = this.themeColors[theme]
|
|
},
|
|
refreshStats: function(loopMode) {
|
|
return api.status().then(function(data) {
|
|
if (loopMode && !vm.itemSelected) vm.refreshItems()
|
|
|
|
vm.loading.feeds = data.running
|
|
if (data.running) {
|
|
setTimeout(vm.refreshStats.bind(vm, true), 500)
|
|
}
|
|
vm.feedStats = data.stats.reduce(function(acc, stat) {
|
|
acc[stat.feed_id] = stat
|
|
return acc
|
|
}, {})
|
|
|
|
api.feeds.list_errors().then(function(errors) {
|
|
vm.feed_errors = errors
|
|
})
|
|
})
|
|
},
|
|
getItemsQuery: function() {
|
|
var query = {}
|
|
if (this.feedSelected) {
|
|
var parts = this.feedSelected.split(':', 2)
|
|
var type = parts[0]
|
|
var guid = parts[1]
|
|
if (type == 'feed') {
|
|
query.feed_id = guid
|
|
} else if (type == 'folder') {
|
|
query.folder_id = guid
|
|
}
|
|
}
|
|
if (this.filterSelected) {
|
|
query.status = this.filterSelected
|
|
}
|
|
if (this.itemSearch) {
|
|
query.search = this.itemSearch
|
|
}
|
|
if (!this.itemSortNewestFirst) {
|
|
query.oldest_first = true
|
|
}
|
|
return query
|
|
},
|
|
refreshFeeds: function() {
|
|
return Promise
|
|
.all([api.folders.list(), api.feeds.list()])
|
|
.then(function(values) {
|
|
vm.folders = values[0]
|
|
vm.feeds = values[1]
|
|
})
|
|
},
|
|
refreshItems: function(loadMore = false) {
|
|
if (this.feedSelected === null) {
|
|
vm.items = []
|
|
vm.itemsHasMore = false
|
|
return
|
|
}
|
|
|
|
var query = this.getItemsQuery()
|
|
if (loadMore) {
|
|
query.after = vm.items[vm.items.length-1].id
|
|
}
|
|
|
|
this.loading.items = true
|
|
return api.items.list(query).then(function(data) {
|
|
if (loadMore) {
|
|
vm.items = vm.items.concat(data.list)
|
|
} else {
|
|
vm.items = data.list
|
|
}
|
|
vm.itemsHasMore = data.has_more
|
|
vm.loading.items = false
|
|
|
|
// load more if there's some space left at the bottom of the item list.
|
|
vm.$nextTick(function() {
|
|
if (vm.itemsHasMore && !vm.loading.items && vm.itemListCloseToBottom()) {
|
|
vm.refreshItems(true)
|
|
}
|
|
})
|
|
})
|
|
},
|
|
itemListCloseToBottom: function() {
|
|
// approx. vertical space at the bottom of the list (loading el & paddings) when 1rem = 16px
|
|
var bottomSpace = 70
|
|
var scale = (parseFloat(getComputedStyle(document.documentElement).fontSize) || 16) / 16
|
|
|
|
var el = this.$refs.itemlist
|
|
|
|
if (el.scrollHeight === 0) return false // element is invisible (responsive design)
|
|
|
|
var closeToBottom = (el.scrollHeight - el.scrollTop - el.offsetHeight) < bottomSpace * scale
|
|
return closeToBottom
|
|
},
|
|
loadMoreItems: function(event, el) {
|
|
if (!this.itemsHasMore) return
|
|
if (this.loading.items) return
|
|
if (this.itemListCloseToBottom()) return this.refreshItems(true)
|
|
if (this.itemSelected && this.itemSelected === this.items[this.items.length - 1].id) return this.refreshItems(true)
|
|
},
|
|
markItemsRead: function() {
|
|
var query = this.getItemsQuery()
|
|
api.items.mark_read(query).then(function() {
|
|
vm.items = []
|
|
vm.itemsPage = {'cur': 1, 'num': 1}
|
|
vm.itemSelected = null
|
|
vm.itemsHasMore = false
|
|
vm.refreshStats()
|
|
})
|
|
},
|
|
toggleFolderExpanded: function (folder) {
|
|
folder.is_expanded = !folder.is_expanded
|
|
api.folders.update(folder.id, {is_expanded: folder.is_expanded})
|
|
},
|
|
formatDate: function(datestr) {
|
|
var options = {
|
|
year: "numeric", month: "long", day: "numeric",
|
|
hour: '2-digit', minute: '2-digit',
|
|
}
|
|
return new Date(datestr).toLocaleDateString(undefined, options)
|
|
},
|
|
moveFeed: function(feed, folder) {
|
|
var folder_id = folder ? folder.id : null
|
|
api.feeds.update(feed.id, {folder_id: folder_id}).then(function() {
|
|
feed.folder_id = folder_id
|
|
vm.refreshStats()
|
|
})
|
|
},
|
|
moveFeedToNewFolder: function(feed) {
|
|
var title = prompt(this.$t('prompt_folder_name'))
|
|
if (!title) return
|
|
api.folders.create({'title': title}).then(function(folder) {
|
|
api.feeds.update(feed.id, {folder_id: folder.id}).then(function() {
|
|
vm.refreshFeeds().then(function() {
|
|
vm.refreshStats()
|
|
})
|
|
})
|
|
})
|
|
},
|
|
createNewFeedFolder: function() {
|
|
var title = prompt(this.$t('prompt_folder_name'))
|
|
if (!title) return
|
|
api.folders.create({'title': title}).then(function(result) {
|
|
vm.refreshFeeds().then(function() {
|
|
vm.$nextTick(function() {
|
|
if (vm.$refs.newFeedFolder) {
|
|
vm.$refs.newFeedFolder.value = result.id
|
|
}
|
|
})
|
|
})
|
|
})
|
|
},
|
|
renameFolder: function(folder) {
|
|
var newTitle = prompt(this.$t('prompt_new_title'), folder.title)
|
|
if (newTitle) {
|
|
api.folders.update(folder.id, {title: newTitle}).then(function() {
|
|
folder.title = newTitle
|
|
this.folders.sort(function(a, b) {
|
|
return a.title.localeCompare(b.title)
|
|
})
|
|
}.bind(this))
|
|
}
|
|
},
|
|
deleteFolder: function(folder) {
|
|
if (confirm(this.$t('confirm_delete', {name: folder.title}))) {
|
|
api.folders.delete(folder.id).then(function() {
|
|
vm.feedSelected = null
|
|
vm.refreshStats()
|
|
vm.refreshFeeds()
|
|
})
|
|
}
|
|
},
|
|
updateFeedLink: function(feed) {
|
|
var newLink = prompt(this.$t('prompt_feed_link'), feed.feed_link)
|
|
if (newLink) {
|
|
api.feeds.update(feed.id, {feed_link: newLink}).then(function() {
|
|
feed.feed_link = newLink
|
|
})
|
|
}
|
|
},
|
|
renameFeed: function(feed) {
|
|
var newTitle = prompt(this.$t('prompt_new_title'), feed.title)
|
|
if (newTitle) {
|
|
api.feeds.update(feed.id, {title: newTitle}).then(function() {
|
|
feed.title = newTitle
|
|
})
|
|
}
|
|
},
|
|
deleteFeed: function(feed) {
|
|
if (confirm(this.$t('confirm_delete', {name: feed.title}))) {
|
|
api.feeds.delete(feed.id).then(function() {
|
|
vm.feedSelected = null
|
|
vm.refreshStats()
|
|
vm.refreshFeeds()
|
|
})
|
|
}
|
|
},
|
|
createFeed: function($event) {
|
|
var form = $event.target
|
|
var data = {
|
|
url: form.querySelector('input[name=url]').value,
|
|
folder_id: parseInt(form.querySelector('select[name=folder_id]').value) || null,
|
|
}
|
|
if (this.feedNewChoiceSelected) {
|
|
var choice = this.feedNewChoice.find(c => c.url === this.feedNewChoiceSelected)
|
|
data.url = this.feedNewChoiceSelected
|
|
if (choice && choice.title_override) data.title_override = choice.title_override
|
|
}
|
|
this.loading.newfeed = true
|
|
api.feeds.create(data).then(function(result) {
|
|
if (result.status === 'success') {
|
|
vm.refreshFeeds()
|
|
vm.refreshStats()
|
|
vm.settings = ''
|
|
vm.feedSelected = 'feed:' + result.feed.id
|
|
} else if (result.status === 'multiple') {
|
|
vm.feedNewChoice = result.choice
|
|
vm.feedNewChoiceSelected = result.choice[0].url
|
|
} else {
|
|
alert('No feeds found at the given url.')
|
|
}
|
|
vm.loading.newfeed = false
|
|
})
|
|
},
|
|
toggleItemStatus: function(item, targetstatus, fallbackstatus) {
|
|
var oldstatus = item.status
|
|
var newstatus = item.status !== targetstatus ? targetstatus : fallbackstatus
|
|
|
|
var updateStats = function(status, incr) {
|
|
if ((status == 'unread') || (status == 'starred')) {
|
|
this.feedStats[item.feed_id][status] += incr
|
|
}
|
|
}.bind(this)
|
|
|
|
api.items.update(item.id, {status: newstatus}).then(function() {
|
|
updateStats(oldstatus, -1)
|
|
updateStats(newstatus, +1)
|
|
|
|
var itemInList = this.items.find(function(i) { return i.id == item.id })
|
|
if (itemInList) itemInList.status = newstatus
|
|
item.status = newstatus
|
|
}.bind(this))
|
|
},
|
|
toggleItemStarred: function(item) {
|
|
this.toggleItemStatus(item, 'starred', 'read')
|
|
},
|
|
toggleItemRead: function(item) {
|
|
this.toggleItemStatus(item, 'unread', 'read')
|
|
},
|
|
importOPML: function(event) {
|
|
var input = event.target
|
|
var form = document.querySelector('#opml-import-form')
|
|
this.$refs.menuDropdown.hide()
|
|
api.upload_opml(form).then(function() {
|
|
input.value = ''
|
|
vm.refreshFeeds()
|
|
vm.refreshStats()
|
|
})
|
|
},
|
|
logout: function() {
|
|
api.logout().then(function() {
|
|
document.location.reload()
|
|
})
|
|
},
|
|
toggleReadability: function() {
|
|
if (this.itemSelectedReadability) {
|
|
this.itemSelectedReadability = null
|
|
return
|
|
}
|
|
var item = this.itemSelectedDetails
|
|
if (!item) return
|
|
if (item.link) {
|
|
this.loading.readability = true
|
|
api.crawl(item.link).then(function(data) {
|
|
vm.itemSelectedReadability = data && data.content
|
|
vm.loading.readability = false
|
|
})
|
|
}
|
|
},
|
|
showSettings: function(settings) {
|
|
this.settings = settings
|
|
|
|
if (settings === 'create') {
|
|
vm.feedNewChoice = []
|
|
vm.feedNewChoiceSelected = ''
|
|
}
|
|
},
|
|
resizeFeedList: function(width) {
|
|
this.feedListWidth = Math.min(Math.max(200, width), 700)
|
|
},
|
|
resizeItemList: function(width) {
|
|
this.itemListWidth = Math.min(Math.max(200, width), 700)
|
|
},
|
|
resetFeedChoice: function() {
|
|
this.feedNewChoice = []
|
|
this.feedNewChoiceSelected = ''
|
|
},
|
|
incrFont: function(x) {
|
|
this.theme.size = +(this.theme.size + (0.1 * x)).toFixed(1)
|
|
},
|
|
fetchAllFeeds: function() {
|
|
if (this.loading.feeds) return
|
|
api.feeds.refresh().then(function() {
|
|
vm.refreshStats()
|
|
})
|
|
},
|
|
computeStats: function() {
|
|
var filter = this.filterSelected
|
|
if (!filter) {
|
|
this.filteredFeedStats = {}
|
|
this.filteredFolderStats = {}
|
|
this.filteredTotalStats = null
|
|
return
|
|
}
|
|
|
|
var statsFeeds = {}, statsFolders = {}, statsTotal = 0
|
|
|
|
for (var i = 0; i < this.feeds.length; i++) {
|
|
var feed = this.feeds[i]
|
|
if (!this.feedStats[feed.id]) continue
|
|
|
|
var n = vm.feedStats[feed.id][filter] || 0
|
|
|
|
if (!statsFolders[feed.folder_id]) statsFolders[feed.folder_id] = 0
|
|
|
|
statsFeeds[feed.id] = n
|
|
statsFolders[feed.folder_id] += n
|
|
statsTotal += n
|
|
}
|
|
|
|
this.filteredFeedStats = statsFeeds
|
|
this.filteredFolderStats = statsFolders
|
|
this.filteredTotalStats = statsTotal
|
|
},
|
|
// navigation helper, navigate relative to selected item
|
|
navigateToItem: function(relativePosition) {
|
|
let vm = this
|
|
if (vm.itemSelected == null) {
|
|
// if no item is selected, select first
|
|
if (vm.items.length !== 0) vm.itemSelected = vm.items[0].id
|
|
return
|
|
}
|
|
|
|
var itemPosition = vm.items.findIndex(function(x) { return x.id === vm.itemSelected })
|
|
if (itemPosition === -1) {
|
|
if (vm.items.length !== 0) vm.itemSelected = vm.items[0].id
|
|
return
|
|
}
|
|
|
|
var newPosition = itemPosition + relativePosition
|
|
if (newPosition < 0 || newPosition >= vm.items.length) return
|
|
|
|
vm.itemSelected = vm.items[newPosition].id
|
|
|
|
vm.$nextTick(function() {
|
|
var scroll = document.querySelector('#item-list-scroll')
|
|
|
|
var handle = scroll.querySelector('input[type=radio]:checked')
|
|
var target = handle && handle.parentElement
|
|
|
|
if (target && scroll) scrollto(target, scroll)
|
|
|
|
vm.loadMoreItems()
|
|
})
|
|
},
|
|
// navigation helper, navigate relative to selected feed
|
|
navigateToFeed: function(relativePosition) {
|
|
let vm = this
|
|
const navigationList = this.foldersWithFeeds
|
|
.filter(folder => !folder.id || !vm.mustHideFolder(folder))
|
|
.map((folder) => {
|
|
if (this.mustHideFolder(folder)) return []
|
|
const folds = folder.id ? [`folder:${folder.id}`] : []
|
|
const feeds = (folder.is_expanded || !folder.id)
|
|
? (folder.feeds || []).filter(f => !vm.mustHideFeed(f)).map(f => `feed:${f.id}`)
|
|
: []
|
|
return folds.concat(feeds)
|
|
})
|
|
.flat()
|
|
navigationList.unshift('')
|
|
|
|
var currentFeedPosition = navigationList.indexOf(vm.feedSelected)
|
|
|
|
if (currentFeedPosition == -1) {
|
|
vm.feedSelected = ''
|
|
return
|
|
}
|
|
|
|
var newPosition = currentFeedPosition+relativePosition
|
|
if (newPosition < 0 || newPosition >= navigationList.length) return
|
|
|
|
vm.feedSelected = navigationList[newPosition]
|
|
|
|
vm.$nextTick(function() {
|
|
var scroll = document.querySelector('#feed-list-scroll')
|
|
|
|
var handle = scroll.querySelector('input[type=radio]:checked')
|
|
var target = handle && handle.parentElement
|
|
|
|
if (target && scroll) scrollto(target, scroll)
|
|
})
|
|
},
|
|
changeRefreshRate: function(offset) {
|
|
const curIdx = this.refreshRateOptions.findIndex(o => o.value === this.refreshRate)
|
|
if (curIdx <= 0 && offset < 0) return
|
|
if (curIdx >= (this.refreshRateOptions.length - 1) && offset > 0) return
|
|
this.refreshRate = this.refreshRateOptions[curIdx + offset].value
|
|
},
|
|
mustHideFolder: function (folder) {
|
|
return this.filterSelected
|
|
&& !(this.current.folder.id == folder.id || this.current.feed.folder_id == folder.id)
|
|
&& !this.filteredFolderStats[folder.id]
|
|
&& (!this.itemSelectedDetails || (this.feedsById[this.itemSelectedDetails.feed_id] || {}).folder_id != folder.id)
|
|
},
|
|
mustHideFeed: function (feed) {
|
|
return this.filterSelected
|
|
&& !(this.current.feed.id == feed.id)
|
|
&& !this.filteredFeedStats[feed.id]
|
|
&& (!this.itemSelectedDetails || this.itemSelectedDetails.feed_id != feed.id)
|
|
},
|
|
changeLanguage(lang) {
|
|
this.$setLang(lang)
|
|
this.language = lang
|
|
api.settings.update({language: lang})
|
|
}
|
|
}
|
|
}
|