mirror of
https://github.com/FreeTubeApp/FreeTube
synced 2024-11-22 01:45:40 +01:00
Compare commits
39 Commits
98f4b6f751
...
b166acafa0
Author | SHA1 | Date | |
---|---|---|---|
|
b166acafa0 | ||
|
a70a5c6ab2 | ||
|
e9b539face | ||
|
a2914fa6ff | ||
|
69ed872ee7 | ||
|
2039016ac1 | ||
|
abbd3493ce | ||
|
3504424eb1 | ||
|
13ba73e6f1 | ||
|
b162507811 | ||
|
094b2c293b | ||
|
e5b5b43621 | ||
|
a81a8b5873 | ||
|
85e46b5bc5 | ||
|
039fce110d | ||
|
4736723e02 | ||
|
33d2d50017 | ||
|
7f6a4ad340 | ||
|
d6429d58bb | ||
|
ede347a2d8 | ||
|
d9d8c9cc1b | ||
|
c07505890e | ||
|
5597720028 | ||
|
50dc04b70a | ||
|
709caef699 | ||
|
5ee7638398 | ||
|
78d1b9c4cb | ||
|
392fa75a87 | ||
|
7896b7b79e | ||
|
88ec2a82cb | ||
|
0572d33cd2 | ||
|
f7b8f1ab55 | ||
|
871e3307fc | ||
|
c254875e2e | ||
|
e60cd786e9 | ||
|
a3057c606a | ||
|
a12c4ea894 | ||
|
808cadb0b9 | ||
|
920bf92fc2 |
@ -25,10 +25,12 @@ const IpcChannels = {
|
||||
DB_HISTORY: 'db-history',
|
||||
DB_PROFILES: 'db-profiles',
|
||||
DB_PLAYLISTS: 'db-playlists',
|
||||
DB_SEARCH_HISTORY: 'db-search-history',
|
||||
DB_SUBSCRIPTION_CACHE: 'db-subscription-cache',
|
||||
|
||||
SYNC_SETTINGS: 'sync-settings',
|
||||
SYNC_HISTORY: 'sync-history',
|
||||
SYNC_SEARCH_HISTORY: 'sync-search-history',
|
||||
SYNC_PROFILES: 'sync-profiles',
|
||||
SYNC_PLAYLISTS: 'sync-playlists',
|
||||
SYNC_SUBSCRIPTION_CACHE: 'sync-subscription-cache',
|
||||
|
@ -211,6 +211,32 @@ class Playlists {
|
||||
}
|
||||
}
|
||||
|
||||
class SearchHistory {
|
||||
static create(pageBookmark) {
|
||||
return db.searchHistory.insertAsync(pageBookmark)
|
||||
}
|
||||
|
||||
static find() {
|
||||
return db.searchHistory.findAsync({})
|
||||
}
|
||||
|
||||
static upsert(pageBookmark) {
|
||||
return db.searchHistory.updateAsync({ _id: pageBookmark._id }, pageBookmark, { upsert: true })
|
||||
}
|
||||
|
||||
static delete(_id) {
|
||||
return db.searchHistory.removeAsync({ _id: _id })
|
||||
}
|
||||
|
||||
static deleteMultiple(ids) {
|
||||
return db.searchHistory.removeAsync({ _id: { $in: ids } })
|
||||
}
|
||||
|
||||
static deleteAll() {
|
||||
return db.searchHistory.removeAsync({}, { multi: true })
|
||||
}
|
||||
}
|
||||
|
||||
class SubscriptionCache {
|
||||
static find() {
|
||||
return db.subscriptionCache.findAsync({})
|
||||
@ -296,6 +322,7 @@ function compactAllDatastores() {
|
||||
db.history.compactDatafileAsync(),
|
||||
db.profiles.compactDatafileAsync(),
|
||||
db.playlists.compactDatafileAsync(),
|
||||
db.searchHistory.compactDatafileAsync(),
|
||||
db.subscriptionCache.compactDatafileAsync(),
|
||||
])
|
||||
}
|
||||
@ -305,6 +332,7 @@ export {
|
||||
History as history,
|
||||
Profiles as profiles,
|
||||
Playlists as playlists,
|
||||
SearchHistory as searchHistory,
|
||||
SubscriptionCache as subscriptionCache,
|
||||
|
||||
compactAllDatastores,
|
||||
|
@ -218,6 +218,50 @@ class Playlists {
|
||||
}
|
||||
}
|
||||
|
||||
class SearchHistory {
|
||||
static create(pageBookmark) {
|
||||
return ipcRenderer.invoke(
|
||||
IpcChannels.DB_SEARCH_HISTORY,
|
||||
{ action: DBActions.GENERAL.CREATE, data: pageBookmark }
|
||||
)
|
||||
}
|
||||
|
||||
static find() {
|
||||
return ipcRenderer.invoke(
|
||||
IpcChannels.DB_SEARCH_HISTORY,
|
||||
{ action: DBActions.GENERAL.FIND }
|
||||
)
|
||||
}
|
||||
|
||||
static upsert(pageBookmark) {
|
||||
return ipcRenderer.invoke(
|
||||
IpcChannels.DB_SEARCH_HISTORY,
|
||||
{ action: DBActions.GENERAL.UPSERT, data: pageBookmark }
|
||||
)
|
||||
}
|
||||
|
||||
static delete(_id) {
|
||||
return ipcRenderer.invoke(
|
||||
IpcChannels.DB_SEARCH_HISTORY,
|
||||
{ action: DBActions.GENERAL.DELETE, data: _id }
|
||||
)
|
||||
}
|
||||
|
||||
static deleteMultiple(ids) {
|
||||
return ipcRenderer.invoke(
|
||||
IpcChannels.DB_SEARCH_HISTORY,
|
||||
{ action: DBActions.GENERAL.DELETE_MULTIPLE, data: ids }
|
||||
)
|
||||
}
|
||||
|
||||
static deleteAll() {
|
||||
return ipcRenderer.invoke(
|
||||
IpcChannels.DB_SEARCH_HISTORY,
|
||||
{ action: DBActions.GENERAL.DELETE_ALL }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SubscriptionCache {
|
||||
static find() {
|
||||
return ipcRenderer.invoke(
|
||||
@ -296,5 +340,6 @@ export {
|
||||
History as history,
|
||||
Profiles as profiles,
|
||||
Playlists as playlists,
|
||||
SearchHistory as searchHistory,
|
||||
SubscriptionCache as subscriptionCache,
|
||||
}
|
||||
|
@ -3,5 +3,6 @@ export {
|
||||
history as DBHistoryHandlers,
|
||||
profiles as DBProfileHandlers,
|
||||
playlists as DBPlaylistHandlers,
|
||||
searchHistory as DBSearchHistoryHandlers,
|
||||
subscriptionCache as DBSubscriptionCacheHandlers,
|
||||
} from 'DB_HANDLERS_ELECTRON_RENDERER_OR_WEB'
|
||||
|
@ -122,6 +122,32 @@ class Playlists {
|
||||
}
|
||||
}
|
||||
|
||||
class SearchHistory {
|
||||
static create(pageBookmark) {
|
||||
return baseHandlers.searchHistory.create(pageBookmark)
|
||||
}
|
||||
|
||||
static find() {
|
||||
return baseHandlers.searchHistory.find()
|
||||
}
|
||||
|
||||
static upsert(pageBookmark) {
|
||||
return baseHandlers.searchHistory.upsert(pageBookmark)
|
||||
}
|
||||
|
||||
static delete(_id) {
|
||||
return baseHandlers.searchHistory.delete(_id)
|
||||
}
|
||||
|
||||
static deleteMultiple(ids) {
|
||||
return baseHandlers.searchHistory.deleteMultiple(ids)
|
||||
}
|
||||
|
||||
static deleteAll() {
|
||||
return baseHandlers.searchHistory.deleteAll()
|
||||
}
|
||||
}
|
||||
|
||||
class SubscriptionCache {
|
||||
static find() {
|
||||
return baseHandlers.subscriptionCache.find()
|
||||
@ -180,5 +206,6 @@ export {
|
||||
History as history,
|
||||
Profiles as profiles,
|
||||
Playlists as playlists,
|
||||
SearchHistory as searchHistory,
|
||||
SubscriptionCache as subscriptionCache,
|
||||
}
|
||||
|
@ -26,4 +26,5 @@ export const settings = new Datastore({ filename: dbPath('settings'), autoload:
|
||||
export const profiles = new Datastore({ filename: dbPath('profiles'), autoload: true })
|
||||
export const playlists = new Datastore({ filename: dbPath('playlists'), autoload: true })
|
||||
export const history = new Datastore({ filename: dbPath('history'), autoload: true })
|
||||
export const searchHistory = new Datastore({ filename: dbPath('search-history'), autoload: true })
|
||||
export const subscriptionCache = new Datastore({ filename: dbPath('subscription-cache'), autoload: true })
|
||||
|
@ -1316,6 +1316,64 @@ function runApp() {
|
||||
}
|
||||
})
|
||||
|
||||
// ************** //
|
||||
// Search History
|
||||
ipcMain.handle(IpcChannels.DB_SEARCH_HISTORY, async (event, { action, data }) => {
|
||||
try {
|
||||
switch (action) {
|
||||
case DBActions.GENERAL.CREATE: {
|
||||
const pageBookmark = await baseHandlers.searchHistory.create(data)
|
||||
syncOtherWindows(
|
||||
IpcChannels.SYNC_SEARCH_HISTORY,
|
||||
event,
|
||||
{ event: SyncEvents.GENERAL.CREATE, data }
|
||||
)
|
||||
return pageBookmark
|
||||
}
|
||||
case DBActions.GENERAL.FIND:
|
||||
return await baseHandlers.searchHistory.find()
|
||||
|
||||
case DBActions.GENERAL.UPSERT:
|
||||
await baseHandlers.searchHistory.upsert(data)
|
||||
syncOtherWindows(
|
||||
IpcChannels.SYNC_SEARCH_HISTORY,
|
||||
event,
|
||||
{ event: SyncEvents.GENERAL.UPSERT, data }
|
||||
)
|
||||
return null
|
||||
|
||||
case DBActions.GENERAL.DELETE:
|
||||
await baseHandlers.searchHistory.delete(data)
|
||||
syncOtherWindows(
|
||||
IpcChannels.SYNC_SEARCH_HISTORY,
|
||||
event,
|
||||
{ event: SyncEvents.GENERAL.DELETE, data }
|
||||
)
|
||||
return null
|
||||
|
||||
case DBActions.GENERAL.DELETE_MULTIPLE:
|
||||
await baseHandlers.searchHistory.deleteMultiple(data)
|
||||
syncOtherWindows(
|
||||
IpcChannels.SYNC_SEARCH_HISTORY,
|
||||
event,
|
||||
{ event: SyncEvents.GENERAL.DELETE_MULTIPLE, data }
|
||||
)
|
||||
return null
|
||||
|
||||
case DBActions.GENERAL.DELETE_ALL:
|
||||
await baseHandlers.searchHistory.deleteAll()
|
||||
return null
|
||||
|
||||
default:
|
||||
// eslint-disable-next-line no-throw-literal
|
||||
throw 'invalid search history db action'
|
||||
}
|
||||
} catch (err) {
|
||||
if (typeof err === 'string') throw err
|
||||
else throw err.toString()
|
||||
}
|
||||
})
|
||||
|
||||
// *********** //
|
||||
|
||||
// *********** //
|
||||
|
@ -10,6 +10,7 @@ import FtToast from './components/ft-toast/ft-toast.vue'
|
||||
import FtProgressBar from './components/FtProgressBar/FtProgressBar.vue'
|
||||
import FtPlaylistAddVideoPrompt from './components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.vue'
|
||||
import FtCreatePlaylistPrompt from './components/ft-create-playlist-prompt/ft-create-playlist-prompt.vue'
|
||||
import PageBookmarkPrompt from './components/page-bookmark-prompt/page-bookmark-prompt.vue'
|
||||
import FtSearchFilters from './components/ft-search-filters/ft-search-filters.vue'
|
||||
import { marked } from 'marked'
|
||||
import { IpcChannels } from '../constants'
|
||||
@ -32,6 +33,7 @@ export default defineComponent({
|
||||
FtProgressBar,
|
||||
FtPlaylistAddVideoPrompt,
|
||||
FtCreatePlaylistPrompt,
|
||||
PageBookmarkPrompt,
|
||||
FtSearchFilters
|
||||
},
|
||||
data: function () {
|
||||
@ -40,6 +42,7 @@ export default defineComponent({
|
||||
showUpdatesBanner: false,
|
||||
showBlogBanner: false,
|
||||
showReleaseNotes: false,
|
||||
pageBookmarksAvailable: false,
|
||||
updateBannerMessage: '',
|
||||
blogBannerMessage: '',
|
||||
latestBlogUrl: '',
|
||||
@ -77,6 +80,9 @@ export default defineComponent({
|
||||
showCreatePlaylistPrompt: function () {
|
||||
return this.$store.getters.getShowCreatePlaylistPrompt
|
||||
},
|
||||
showPageBookmarkPrompt: function () {
|
||||
return this.$store.getters.getShowPageBookmarkPrompt
|
||||
},
|
||||
showSearchFilters: function () {
|
||||
return this.$store.getters.getShowSearchFilters
|
||||
},
|
||||
@ -142,7 +148,7 @@ export default defineComponent({
|
||||
|
||||
externalLinkHandling: function () {
|
||||
return this.$store.getters.getExternalLinkHandling
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
windowTitle: 'setWindowTitle',
|
||||
@ -178,6 +184,9 @@ export default defineComponent({
|
||||
this.grabAllProfiles(this.$t('Profile.All Channels')).then(async () => {
|
||||
this.grabHistory()
|
||||
this.grabAllPlaylists()
|
||||
this.grabPageBookmarks().then(async () => {
|
||||
this.pageBookmarksAvailable = true
|
||||
})
|
||||
this.grabAllSubscriptions()
|
||||
|
||||
if (process.env.IS_ELECTRON) {
|
||||
@ -552,6 +561,7 @@ export default defineComponent({
|
||||
'grabUserSettings',
|
||||
'grabAllProfiles',
|
||||
'grabHistory',
|
||||
'grabPageBookmarks',
|
||||
'grabAllPlaylists',
|
||||
'grabAllSubscriptions',
|
||||
'getYoutubeUrlInfo',
|
||||
|
@ -56,6 +56,9 @@
|
||||
<ft-create-playlist-prompt
|
||||
v-if="showCreatePlaylistPrompt"
|
||||
/>
|
||||
<page-bookmark-prompt
|
||||
v-if="showPageBookmarkPrompt"
|
||||
/>
|
||||
<ft-toast />
|
||||
<ft-progress-bar
|
||||
v-if="showProgressBar"
|
||||
@ -63,6 +66,7 @@
|
||||
<top-nav
|
||||
ref="topNav"
|
||||
:inert="isPromptOpen"
|
||||
:page-bookmarks-available="pageBookmarksAvailable"
|
||||
/>
|
||||
<side-nav
|
||||
ref="sideNav"
|
||||
|
@ -202,8 +202,22 @@ body[dir='rtl'] .ft-input-component.search.showClearTextButton:focus-within .inp
|
||||
.list li {
|
||||
display: block;
|
||||
padding-block: 0;
|
||||
padding-inline: 15px;
|
||||
line-height: 2rem;
|
||||
padding-inline: 15px;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bookmarkStarIcon {
|
||||
color: var(--favorite-icon-color);
|
||||
}
|
||||
|
||||
.searchResultIcon {
|
||||
opacity: 0.6;
|
||||
padding-inline-end: 10px;
|
||||
inline-size: 16px;
|
||||
block-size: 16px;
|
||||
}
|
||||
|
||||
.hover {
|
||||
|
@ -2,7 +2,9 @@ import { defineComponent } from 'vue'
|
||||
import { mapActions } from 'vuex'
|
||||
|
||||
import FtTooltip from '../ft-tooltip/ft-tooltip.vue'
|
||||
import { isKeyboardEventKeyPrintableChar, isNullOrEmpty } from '../../helpers/strings'
|
||||
import { getIconForRoute, isKeyboardEventKeyPrintableChar, isNullOrEmpty } from '../../helpers/strings'
|
||||
|
||||
const MAX_VISIBLE_LIST_ITEMS = 15
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FtInput',
|
||||
@ -83,7 +85,7 @@ export default defineComponent({
|
||||
isPointerInList: false,
|
||||
keyboardSelectedOptionIndex: -1,
|
||||
},
|
||||
visibleDataList: this.dataList,
|
||||
visibleDataList: this.dataList?.slice(0, MAX_VISIBLE_LIST_ITEMS),
|
||||
// This button should be invisible on app start
|
||||
// As the text input box should be empty
|
||||
clearTextButtonExisting: false,
|
||||
@ -116,8 +118,7 @@ export default defineComponent({
|
||||
|
||||
searchStateKeyboardSelectedOptionValue() {
|
||||
if (this.searchState.keyboardSelectedOptionIndex === -1) { return null }
|
||||
|
||||
return this.visibleDataList[this.searchState.keyboardSelectedOptionIndex]
|
||||
return this.getTextForArrayAtIndex(this.visibleDataList, this.searchState.keyboardSelectedOptionIndex)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
@ -143,6 +144,9 @@ export default defineComponent({
|
||||
this.updateVisibleDataList()
|
||||
},
|
||||
methods: {
|
||||
getTextForArrayAtIndex: function (array, index) {
|
||||
return array[index].name ?? array[index]
|
||||
},
|
||||
handleClick: function (e) {
|
||||
// No action if no input text
|
||||
if (!this.inputDataPresent) {
|
||||
@ -234,7 +238,11 @@ export default defineComponent({
|
||||
|
||||
handleOptionClick: function (index) {
|
||||
this.searchState.showOptions = false
|
||||
this.inputData = this.visibleDataList[index]
|
||||
if (this.visibleDataList[index].route) {
|
||||
this.inputData = `ft:${this.visibleDataList[index].route}`
|
||||
} else {
|
||||
this.inputData = this.visibleDataList[index]
|
||||
}
|
||||
this.$emit('input', this.inputData)
|
||||
this.handleClick()
|
||||
},
|
||||
@ -248,9 +256,11 @@ export default defineComponent({
|
||||
if (this.searchState.selectedOption !== -1) {
|
||||
this.searchState.showOptions = false
|
||||
event.preventDefault()
|
||||
this.inputData = this.visibleDataList[this.searchState.selectedOption]
|
||||
this.inputData = this.getTextForArrayAtIndex(this.visibleDataList, this.searchState.selectedOption)
|
||||
this.handleOptionClick(this.searchState.selectedOption)
|
||||
} else {
|
||||
this.handleClick(event)
|
||||
}
|
||||
this.handleClick(event)
|
||||
// Early return
|
||||
return
|
||||
}
|
||||
@ -296,18 +306,21 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
updateVisibleDataList: function () {
|
||||
if (this.dataList.length === 0) { return }
|
||||
// Reset selected option before it's updated
|
||||
this.searchState.selectedOption = -1
|
||||
this.searchState.keyboardSelectedOptionIndex = -1
|
||||
if (this.inputData === '') {
|
||||
this.visibleDataList = this.dataList
|
||||
this.visibleDataList = this.dataList?.slice(0, MAX_VISIBLE_LIST_ITEMS)
|
||||
return
|
||||
}
|
||||
// get list of items that match input
|
||||
const lowerCaseInputData = this.inputData.toLowerCase()
|
||||
|
||||
this.visibleDataList = this.dataList.filter(x => {
|
||||
this.visibleDataList = this.dataList?.slice(0, MAX_VISIBLE_LIST_ITEMS).filter(x => {
|
||||
if (x.name) {
|
||||
return x.name.toLowerCase().indexOf(lowerCaseInputData) !== -1
|
||||
}
|
||||
|
||||
return x.toLowerCase().indexOf(lowerCaseInputData) !== -1
|
||||
})
|
||||
},
|
||||
@ -316,6 +329,10 @@ export default defineComponent({
|
||||
this.inputData = text
|
||||
},
|
||||
|
||||
iconForBookmarkedPage: (pageBookmark) => {
|
||||
return getIconForRoute(pageBookmark.route) ?? ['fas', 'magnifying-glass']
|
||||
},
|
||||
|
||||
focus() {
|
||||
this.$refs.input.focus()
|
||||
},
|
||||
|
@ -75,14 +75,25 @@
|
||||
>
|
||||
<!-- eslint-disable vuejs-accessibility/click-events-have-key-events -->
|
||||
<li
|
||||
v-for="(list, index) in visibleDataList"
|
||||
v-for="(entry, index) in visibleDataList"
|
||||
:key="index"
|
||||
:class="searchState.selectedOption === index ? 'hover': ''"
|
||||
:class="{ hover: searchState.selectedOption === index }"
|
||||
:aria-roledescription="entry.isBookmark ? $t('Role Descriptions.bookmark') : null"
|
||||
@click="handleOptionClick(index)"
|
||||
@mouseenter="searchState.selectedOption = index"
|
||||
@mouseleave="searchState.selectedOption = -1"
|
||||
>
|
||||
{{ list }}
|
||||
<font-awesome-icon
|
||||
v-if="entry.isBookmark"
|
||||
:icon="iconForBookmarkedPage(entry)"
|
||||
class="searchResultIcon bookmarkStarIcon"
|
||||
/>
|
||||
<font-awesome-icon
|
||||
v-else-if="isSearch"
|
||||
:icon="['fas', 'magnifying-glass']"
|
||||
class="searchResultIcon"
|
||||
/>
|
||||
{{ entry.name ?? entry }}
|
||||
</li>
|
||||
<!-- skipped -->
|
||||
</ul>
|
||||
|
@ -0,0 +1,9 @@
|
||||
.heading {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pageBookmarkNameInput {
|
||||
inline-size: 80%;
|
||||
max-inline-size: 600px;
|
||||
margin-inline: auto;
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
import { defineComponent, nextTick } from 'vue'
|
||||
import { mapActions } from 'vuex'
|
||||
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
|
||||
import FtPrompt from '../ft-prompt/ft-prompt.vue'
|
||||
import FtButton from '../ft-button/ft-button.vue'
|
||||
import FtInput from '../../components/ft-input/ft-input.vue'
|
||||
import { showToast } from '../../helpers/utils'
|
||||
import { defaultBookmarkNameForRoute } from '../../helpers/strings'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PageBookmarkPrompt',
|
||||
components: {
|
||||
'ft-flex-box': FtFlexBox,
|
||||
'ft-prompt': FtPrompt,
|
||||
'ft-button': FtButton,
|
||||
'ft-input': FtInput
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
name: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isBookmarkBeingCreated: function () {
|
||||
return this.pageBookmark == null
|
||||
},
|
||||
pageBookmarks: function () {
|
||||
return this.$store.getters.getPageBookmarks
|
||||
},
|
||||
duplicateNameCount: function () {
|
||||
const currentBookmarkAdjustment = this.name === this.pageBookmark?.name ? -1 : 0
|
||||
return currentBookmarkAdjustment + this.pageBookmarks.filter((pageBookmark) => pageBookmark.name === this.name).length
|
||||
},
|
||||
duplicateNameMessage: function () {
|
||||
return this.$tc('Page Bookmark["There is {count} other bookmark with the same name."]', this.duplicateNameCount, { count: this.duplicateNameCount })
|
||||
},
|
||||
pageBookmark: function () {
|
||||
return this.$store.getters.getPageBookmarkWithRoute(this.$router.currentRoute.fullPath)
|
||||
},
|
||||
title: function () {
|
||||
return this.isBookmarkBeingCreated ? this.$t('Page Bookmark.Create Bookmark') : this.$t('Page Bookmark.Edit Bookmark')
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
nextTick(() => {
|
||||
this.name = this.pageBookmark?.name ?? defaultBookmarkNameForRoute(this.$router.currentRoute)
|
||||
this.$refs.pageBookmarkNameInput?.focus()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
hide: function () {
|
||||
this.hidePageBookmarkPrompt()
|
||||
},
|
||||
|
||||
removeBookmark: function () {
|
||||
const pageBookmark = this.pageBookmark
|
||||
this.removePageBookmark(pageBookmark._id)
|
||||
showToast(this.$t('Page Bookmark.Removed page bookmark', { name: pageBookmark.name }))
|
||||
this.hide()
|
||||
},
|
||||
|
||||
save: function () {
|
||||
const pageBookmark = {
|
||||
route: this.$router.currentRoute.fullPath,
|
||||
name: this.name,
|
||||
isBookmark: true
|
||||
}
|
||||
|
||||
if (this.isBookmarkBeingCreated) {
|
||||
this.createPageBookmark(pageBookmark)
|
||||
showToast(this.$t('Page Bookmark.Created page bookmark', { name: this.name }))
|
||||
} else if (this.pageBookmark.name !== pageBookmark.name) {
|
||||
this.updatePageBookmark(pageBookmark)
|
||||
showToast(this.$t('Page Bookmark.Updated page bookmark', { name: this.name }))
|
||||
}
|
||||
|
||||
this.hide()
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'hidePageBookmarkPrompt',
|
||||
'createPageBookmark',
|
||||
'removePageBookmark',
|
||||
'updatePageBookmark'
|
||||
])
|
||||
}
|
||||
})
|
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<ft-prompt
|
||||
:label="title"
|
||||
@click="hide"
|
||||
>
|
||||
<h2 class="heading">
|
||||
{{ title }}
|
||||
</h2>
|
||||
<div
|
||||
class="pageBookmarkDetails"
|
||||
>
|
||||
<ft-input
|
||||
ref="pageBookmarkNameInput"
|
||||
class="pageBookmarkNameInput"
|
||||
:placeholder="$t('Name')"
|
||||
:value="name"
|
||||
:show-clear-text-button="true"
|
||||
:show-action-button="false"
|
||||
@input="e => name = e"
|
||||
@clear="e => name = ''"
|
||||
@keydown.enter.native="save"
|
||||
/>
|
||||
<ft-flex-box v-if="duplicateNameCount > 0">
|
||||
<p>{{ duplicateNameMessage }}</p>
|
||||
</ft-flex-box>
|
||||
</div>
|
||||
<div class="actions-container">
|
||||
<ft-flex-box>
|
||||
<ft-button
|
||||
v-if="!isBookmarkBeingCreated"
|
||||
:label="$t('Page Bookmark.Remove Bookmark')"
|
||||
:icon="['fas', 'trash']"
|
||||
text-color="var(--destructive-text-color)"
|
||||
background-color="var(--destructive-color)"
|
||||
@click="removeBookmark"
|
||||
/>
|
||||
<ft-button
|
||||
:label="$t('Save')"
|
||||
:disabled="name === ''"
|
||||
text-color="var(--text-with-accent-color)"
|
||||
background-color="var(--accent-color)"
|
||||
@click="save"
|
||||
/>
|
||||
<ft-button
|
||||
:label="$t('Cancel')"
|
||||
:text-color="null"
|
||||
:background-color="null"
|
||||
@click="hide"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
</div>
|
||||
</ft-prompt>
|
||||
</template>
|
||||
|
||||
<script src="./page-bookmark-prompt.js" />
|
||||
<style scoped src="./page-bookmark-prompt.css" />
|
@ -23,6 +23,7 @@ export default defineComponent({
|
||||
showRemoveHistoryPrompt: false,
|
||||
showRemoveSubscriptionsPrompt: false,
|
||||
showRemovePlaylistsPrompt: false,
|
||||
showRemovePageBookmarksPrompt: false,
|
||||
promptValues: [
|
||||
'delete',
|
||||
'cancel'
|
||||
@ -46,6 +47,9 @@ export default defineComponent({
|
||||
removeSubscriptionsPromptMessage: function () {
|
||||
return this.$t('Settings.Privacy Settings["Are you sure you want to remove all subscriptions and profiles? This cannot be undone."]')
|
||||
},
|
||||
removePageBookmarksPromptMessage: function () {
|
||||
return this.$t('Settings.Privacy Settings["Are you sure you want to remove all page bookmarks? This cannot be undone."]')
|
||||
},
|
||||
promptNames: function () {
|
||||
return [
|
||||
this.$t('Yes, Delete'),
|
||||
@ -115,6 +119,14 @@ export default defineComponent({
|
||||
showToast(this.$t('Settings.Privacy Settings.All playlists have been removed'))
|
||||
},
|
||||
|
||||
handleRemovePageBookmarks: function (option) {
|
||||
this.showRemovePageBookmarksPrompt = false
|
||||
if (option !== 'delete') { return }
|
||||
|
||||
this.removeAllPageBookmarks()
|
||||
showToast(this.$t('Settings.Privacy Settings.All page bookmarks have been removed'))
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'updateRememberHistory',
|
||||
'removeAllHistory',
|
||||
@ -128,6 +140,7 @@ export default defineComponent({
|
||||
'updateAllSubscriptionsList',
|
||||
'updateProfileSubscriptions',
|
||||
'removeAllPlaylists',
|
||||
'removeAllPageBookmarks',
|
||||
'updateQuickBookmarkTargetPlaylistId',
|
||||
])
|
||||
}
|
||||
|
@ -60,6 +60,13 @@
|
||||
:icon="['fas', 'trash']"
|
||||
@click="showRemovePlaylistsPrompt = true"
|
||||
/>
|
||||
<ft-button
|
||||
:label="$t('Settings.Privacy Settings.Remove All Page Bookmarks')"
|
||||
text-color="var(--destructive-text-color)"
|
||||
background-color="var(--destructive-color)"
|
||||
:icon="['fas', 'trash']"
|
||||
@click="showRemovePageBookmarksPrompt = true"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
<ft-prompt
|
||||
v-if="showSearchCachePrompt"
|
||||
@ -93,6 +100,14 @@
|
||||
:is-first-option-destructive="true"
|
||||
@click="handleRemovePlaylists"
|
||||
/>
|
||||
<ft-prompt
|
||||
v-if="showRemovePageBookmarksPrompt"
|
||||
:label="removePageBookmarksPromptMessage"
|
||||
:option-names="promptNames"
|
||||
:option-values="promptValues"
|
||||
:is-first-option-destructive="true"
|
||||
@click="handleRemovePageBookmarks"
|
||||
/>
|
||||
</ft-settings-section>
|
||||
</template>
|
||||
|
||||
|
@ -16,6 +16,12 @@ export default defineComponent({
|
||||
FtInput,
|
||||
FtProfileSelector
|
||||
},
|
||||
props: {
|
||||
pageBookmarksAvailable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data: () => {
|
||||
let isArrowBackwardDisabled = true
|
||||
let isArrowForwardDisabled = true
|
||||
@ -28,10 +34,20 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
isRouteBookmarkable: false,
|
||||
showSearchContainer: true,
|
||||
isArrowBackwardDisabled,
|
||||
isArrowForwardDisabled,
|
||||
currentRouteFullPath: '',
|
||||
searchSuggestionsDataList: [],
|
||||
allowedPageBookmarkRouteMetaTitles: [
|
||||
'Search Results',
|
||||
'Playlist',
|
||||
'Channel',
|
||||
'Watch',
|
||||
'Hashtag',
|
||||
'Settings' // for linkable settings sections
|
||||
],
|
||||
lastSuggestionQuery: ''
|
||||
}
|
||||
},
|
||||
@ -96,14 +112,33 @@ export default defineComponent({
|
||||
|
||||
newWindowText: function () {
|
||||
return this.$t('Open New Window')
|
||||
},
|
||||
|
||||
isPageBookmarked: function () {
|
||||
return this.pageBookmarksAvailable && this.$store.getters.getPageBookmarkWithRoute(this.currentRouteFullPath) != null
|
||||
},
|
||||
|
||||
matchingBookmarksDataList: function () {
|
||||
return this.$store.getters.getPageBookmarksMatchingQuery(this.lastSuggestionQuery, this.currentRouteFullPath)
|
||||
},
|
||||
|
||||
pageBookmarkIconTitle: function () {
|
||||
return this.isPageBookmarked ? this.$t('Edit bookmark for this page') : this.$t('Bookmark this page')
|
||||
},
|
||||
|
||||
pageBookmarkIconTheme: function () {
|
||||
return this.isPageBookmarked ? 'base favorite' : 'base'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route: function () {
|
||||
$route: function (to, from) {
|
||||
if ('navigation' in window) {
|
||||
this.isArrowForwardDisabled = !window.navigation.canGoForward
|
||||
this.isArrowBackwardDisabled = !window.navigation.canGoBack
|
||||
}
|
||||
|
||||
this.currentRouteFullPath = to.fullPath
|
||||
this.isRouteBookmarkable = this.allowedPageBookmarkRouteMetaTitles.includes(to.meta.title)
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
@ -111,7 +146,7 @@ export default defineComponent({
|
||||
if (window.innerWidth <= MOBILE_WIDTH_THRESHOLD) {
|
||||
this.showSearchContainer = false
|
||||
}
|
||||
|
||||
this.currentRouteFullPath = this.$router.currentRoute.fullPath
|
||||
// Store is not up-to-date when the component mounts, so we use timeout.
|
||||
setTimeout(() => {
|
||||
if (this.expandSideBar) {
|
||||
@ -143,6 +178,17 @@ export default defineComponent({
|
||||
|
||||
clearLocalSearchSuggestionsSession()
|
||||
|
||||
if (queryText.startsWith('ft:')) {
|
||||
this.$refs.searchInput.handleClearTextClick()
|
||||
const adjustedQuery = queryText.substring(3)
|
||||
openInternalPath({
|
||||
path: adjustedQuery,
|
||||
adjustedQuery,
|
||||
doCreateNewWindow
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.getYoutubeUrlInfo(queryText).then((result) => {
|
||||
switch (result.urlType) {
|
||||
case 'video': {
|
||||
@ -264,12 +310,15 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
getSearchSuggestionsDebounce: function (query) {
|
||||
const trimmedQuery = query.trim()
|
||||
if (trimmedQuery === this.lastSuggestionQuery) {
|
||||
return
|
||||
}
|
||||
|
||||
this.lastSuggestionQuery = trimmedQuery
|
||||
|
||||
if (this.enableSearchSuggestions) {
|
||||
const trimmedQuery = query.trim()
|
||||
if (trimmedQuery !== this.lastSuggestionQuery) {
|
||||
this.lastSuggestionQuery = trimmedQuery
|
||||
this.debounceSearchResults(trimmedQuery)
|
||||
}
|
||||
this.debounceSearchResults(trimmedQuery)
|
||||
}
|
||||
},
|
||||
|
||||
@ -354,6 +403,7 @@ export default defineComponent({
|
||||
},
|
||||
...mapActions([
|
||||
'getYoutubeUrlInfo',
|
||||
'showPageBookmarkPrompt',
|
||||
'showSearchFilters'
|
||||
])
|
||||
}
|
||||
|
@ -21,9 +21,9 @@
|
||||
inline-size: 100%;
|
||||
z-index: 4;
|
||||
|
||||
@media only screen and (width >= 961px) {
|
||||
@media only screen and (width >= 1162px) {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 440px 1fr;
|
||||
grid-template-columns: 1fr 720px 0.5fr 0.5fr;
|
||||
}
|
||||
|
||||
@include top-nav-is-colored {
|
||||
@ -60,7 +60,8 @@
|
||||
}
|
||||
|
||||
&.arrowBackwardDisabled,
|
||||
&.arrowForwardDisabled {
|
||||
&.arrowForwardDisabled,
|
||||
&.disabled {
|
||||
color: #808080;
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
@ -104,6 +105,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.pageBookmarkIcon {
|
||||
&.favorite {
|
||||
color: var(--favorite-icon-color);
|
||||
|
||||
@include top-nav-is-colored {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.side {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
@ -84,7 +84,7 @@
|
||||
:placeholder="$t('Search / Go to URL')"
|
||||
class="searchInput"
|
||||
:is-search="true"
|
||||
:data-list="searchSuggestionsDataList"
|
||||
:data-list="[...matchingBookmarksDataList, ...searchSuggestionsDataList]"
|
||||
:spellcheck="false"
|
||||
:show-clear-text-button="true"
|
||||
@input="getSearchSuggestionsDebounce"
|
||||
@ -102,6 +102,18 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<font-awesome-icon
|
||||
class="pageBookmarkIcon navIcon"
|
||||
:icon="['fas', 'star']"
|
||||
:title="pageBookmarkIconTitle"
|
||||
:active="isPageBookmarked"
|
||||
:class="{ [pageBookmarkIconTheme]: true, disabled: !pageBookmarksAvailable || !isRouteBookmarkable }"
|
||||
:aria-disabled="!pageBookmarksAvailable || !isRouteBookmarkable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="showPageBookmarkPrompt"
|
||||
@keydown.enter.prevent="showPageBookmarkPrompt"
|
||||
/>
|
||||
<ft-profile-selector class="side profiles" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import i18n from '../i18n/index'
|
||||
import packageDetails from '../../../package.json'
|
||||
|
||||
/**
|
||||
* This will return true if a string is null, undefined or empty.
|
||||
@ -58,6 +59,55 @@ export function translateWindowTitle(title) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getIconForRoute(route) {
|
||||
const routeSlashIndex = route.indexOf('/', 2)
|
||||
const truncatedRoute = (routeSlashIndex === -1) ? route : route.substring(0, routeSlashIndex)
|
||||
switch (truncatedRoute) {
|
||||
case '/subscriptions':
|
||||
return ['fas', 'rss']
|
||||
case '/subscribedchannels':
|
||||
case '/channel':
|
||||
return ['fas', 'list']
|
||||
case '/trending':
|
||||
return ['fas', 'fire']
|
||||
case '/popular':
|
||||
return ['fas', 'users']
|
||||
case '/userplaylists':
|
||||
return ['fas', 'bookmark']
|
||||
case '/history':
|
||||
return ['fas', 'history']
|
||||
case '/settings':
|
||||
return ['fas', 'sliders-h']
|
||||
case '/about':
|
||||
return ['fas', 'info-circle']
|
||||
case '/search':
|
||||
case '/hashtag':
|
||||
return ['fas', 'magnifying-glass']
|
||||
case '/playlist': {
|
||||
const solidOrRegular = route.includes('?playlistType=user') ? 'fas' : 'far'
|
||||
return [solidOrRegular, 'bookmark']
|
||||
} case '/watch':
|
||||
return ['fas', 'play']
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an appropriate default bookmark name
|
||||
* for a given route.
|
||||
* @param {import('vue-router').Route} route
|
||||
* @returns {string}
|
||||
*/
|
||||
export function defaultBookmarkNameForRoute(route) {
|
||||
if (route.meta.title === 'Search Results') {
|
||||
// Use the inputted search query over 'Search Results'
|
||||
return route.params.query
|
||||
}
|
||||
// Remove unnecessary " - FreeTube" appendage
|
||||
return document.title.replace(` - ${packageDetails.productName}`, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first user-perceived character,
|
||||
* respecting language specific rules and
|
||||
|
@ -93,6 +93,7 @@ import {
|
||||
faSortAlphaDown,
|
||||
faSortAlphaDownAlt,
|
||||
faSortDown,
|
||||
faStar,
|
||||
faStepBackward,
|
||||
faStepForward,
|
||||
faSync,
|
||||
@ -206,6 +207,7 @@ library.add(
|
||||
faSortAlphaDown,
|
||||
faSortAlphaDownAlt,
|
||||
faSortDown,
|
||||
faStar,
|
||||
faStepBackward,
|
||||
faStepForward,
|
||||
faSync,
|
||||
|
@ -8,6 +8,7 @@ import invidious from './invidious'
|
||||
import playlists from './playlists'
|
||||
import profiles from './profiles'
|
||||
import settings from './settings'
|
||||
import searchHistory from './search-history'
|
||||
import subscriptionCache from './subscription-cache'
|
||||
import utils from './utils'
|
||||
import player from './player'
|
||||
@ -18,6 +19,7 @@ export default {
|
||||
playlists,
|
||||
profiles,
|
||||
settings,
|
||||
searchHistory,
|
||||
subscriptionCache,
|
||||
utils,
|
||||
player,
|
||||
|
@ -369,8 +369,10 @@ const actions = {
|
||||
}
|
||||
},
|
||||
|
||||
async removeAllPlaylists({ commit }) {
|
||||
async removeAllPlaylists({ commit, dispatch, state }) {
|
||||
try {
|
||||
const playlistIds = state.playlists.map((playlist) => playlist._id)
|
||||
dispatch('removeUserPlaylistPageBookmarks', playlistIds, { root: true })
|
||||
await DBPlaylistHandlers.deleteAll()
|
||||
commit('removeAllPlaylists')
|
||||
} catch (errMessage) {
|
||||
@ -387,8 +389,9 @@ const actions = {
|
||||
}
|
||||
},
|
||||
|
||||
async removePlaylist({ commit }, playlistId) {
|
||||
async removePlaylist({ commit, dispatch }, playlistId) {
|
||||
try {
|
||||
dispatch('removeUserPlaylistPageBookmarks', [playlistId], { root: true })
|
||||
await DBPlaylistHandlers.delete(playlistId)
|
||||
commit('removePlaylist', playlistId)
|
||||
} catch (errMessage) {
|
||||
@ -396,8 +399,9 @@ const actions = {
|
||||
}
|
||||
},
|
||||
|
||||
async removePlaylists({ commit }, playlistIds) {
|
||||
async removePlaylists({ commit, dispatch }, playlistIds) {
|
||||
try {
|
||||
dispatch('removeUserPlaylistPageBookmarks', playlistIds, { root: true })
|
||||
await DBPlaylistHandlers.deleteMultiple(playlistIds)
|
||||
commit('removePlaylists', playlistIds)
|
||||
} catch (errMessage) {
|
||||
|
151
src/renderer/store/modules/search-history.js
Normal file
151
src/renderer/store/modules/search-history.js
Normal file
@ -0,0 +1,151 @@
|
||||
import { DBSearchHistoryHandlers } from '../../../datastores/handlers/index'
|
||||
|
||||
const state = {
|
||||
pageBookmarks: []
|
||||
}
|
||||
|
||||
const getters = {
|
||||
getPageBookmarks: (state) => {
|
||||
return state.pageBookmarks
|
||||
},
|
||||
|
||||
getPageBookmarkWithRoute: (state) => (route) => {
|
||||
const pageBookmark = state.pageBookmarks.find(p => p.route === route)
|
||||
return pageBookmark
|
||||
},
|
||||
|
||||
getPageBookmarksMatchingQuery: (state) => (query, routeToExclude) => {
|
||||
if (query === '') {
|
||||
return []
|
||||
}
|
||||
const queryToLower = query.toLowerCase()
|
||||
return state.pageBookmarks.filter((pageBookmark) =>
|
||||
pageBookmark.name.toLowerCase().includes(queryToLower) && pageBookmark.route !== routeToExclude
|
||||
)
|
||||
},
|
||||
|
||||
getPageBookmarkIdsForMatchingUserPlaylistIds: (state) => (playlistIds) => {
|
||||
const pageBookmarkIds = []
|
||||
const allPageBookmarks = state.pageBookmarks
|
||||
const pageBookmarkLimitedRoutesMap = new Map()
|
||||
allPageBookmarks.forEach((pageBookmark) => {
|
||||
pageBookmarkLimitedRoutesMap.set(pageBookmark.route, pageBookmark._id)
|
||||
})
|
||||
|
||||
playlistIds.forEach((playlistId) => {
|
||||
const route = `/playlist/${playlistId}?playlistType=user&searchQueryText=`
|
||||
if (!pageBookmarkLimitedRoutesMap.has(route)) {
|
||||
return
|
||||
}
|
||||
|
||||
pageBookmarkIds.push(pageBookmarkLimitedRoutesMap.get(route))
|
||||
})
|
||||
|
||||
return pageBookmarkIds
|
||||
}
|
||||
}
|
||||
const actions = {
|
||||
async grabPageBookmarks({ commit }) {
|
||||
try {
|
||||
const results = await DBSearchHistoryHandlers.find()
|
||||
commit('setPageBookmarks', results)
|
||||
} catch (errMessage) {
|
||||
console.error(errMessage)
|
||||
}
|
||||
},
|
||||
|
||||
async createPageBookmark({ commit }, pageBookmark) {
|
||||
try {
|
||||
const newPageBookmark = await DBSearchHistoryHandlers.create(pageBookmark)
|
||||
commit('addPageBookmarkToList', newPageBookmark)
|
||||
} catch (errMessage) {
|
||||
console.error(errMessage)
|
||||
}
|
||||
},
|
||||
|
||||
async updatePageBookmark({ commit }, pageBookmark) {
|
||||
try {
|
||||
await DBSearchHistoryHandlers.upsert(pageBookmark)
|
||||
commit('upsertPageBookmarkToList', pageBookmark)
|
||||
} catch (errMessage) {
|
||||
console.error(errMessage)
|
||||
}
|
||||
},
|
||||
|
||||
async removePageBookmark({ commit }, _id) {
|
||||
try {
|
||||
await DBSearchHistoryHandlers.delete(_id)
|
||||
commit('removePageBookmarkFromList', _id)
|
||||
} catch (errMessage) {
|
||||
console.error(errMessage)
|
||||
}
|
||||
},
|
||||
|
||||
async removePageBookmarks({ commit }, ids) {
|
||||
try {
|
||||
await DBSearchHistoryHandlers.deleteMultiple(ids)
|
||||
commit('removePageBookmarksFromList', ids)
|
||||
} catch (errMessage) {
|
||||
console.error(errMessage)
|
||||
}
|
||||
},
|
||||
|
||||
async removeUserPlaylistPageBookmarks({ dispatch, getters }, userPlaylistIds) {
|
||||
const pageBookmarkIds = getters.getPageBookmarkIdsForMatchingUserPlaylistIds(userPlaylistIds)
|
||||
if (pageBookmarkIds.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
dispatch('removePageBookmarks', pageBookmarkIds)
|
||||
},
|
||||
|
||||
async removeAllPageBookmarks({ commit }) {
|
||||
try {
|
||||
await DBSearchHistoryHandlers.deleteAll()
|
||||
commit('setPageBookmarks', [])
|
||||
} catch (errMessage) {
|
||||
console.error(errMessage)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
addPageBookmarkToList(state, pageBookmark) {
|
||||
state.pageBookmarks.push(pageBookmark)
|
||||
},
|
||||
|
||||
setPageBookmarks(state, pageBookmarks) {
|
||||
state.pageBookmarks = pageBookmarks
|
||||
},
|
||||
|
||||
upsertPageBookmarkToList(state, updatedPageBookmark) {
|
||||
const i = state.pageBookmarks.findIndex((p) => {
|
||||
return p.route === updatedPageBookmark.route
|
||||
})
|
||||
|
||||
if (i === -1) {
|
||||
state.pageBookmarks.push(updatedPageBookmark)
|
||||
} else {
|
||||
state.pageBookmarks.splice(i, 1, updatedPageBookmark)
|
||||
}
|
||||
},
|
||||
|
||||
removePageBookmarkFromList(state, _id) {
|
||||
const i = state.pageBookmarks.findIndex((pageBookmark) => {
|
||||
return pageBookmark._id === _id
|
||||
})
|
||||
|
||||
state.pageBookmarks.splice(i, 1)
|
||||
},
|
||||
|
||||
removePageBookmarksFromList(state, ids) {
|
||||
state.pageBookmarks = state.pageBookmarks.filter((pageBookmark) => !ids.includes(pageBookmark._id))
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
@ -507,6 +507,29 @@ const customActions = {
|
||||
}
|
||||
})
|
||||
|
||||
ipcRenderer.on(IpcChannels.SYNC_SEARCH_HISTORY, (_, { event, data }) => {
|
||||
switch (event) {
|
||||
case SyncEvents.GENERAL.CREATE:
|
||||
commit('addPageBookmarkToList', data)
|
||||
break
|
||||
|
||||
case SyncEvents.GENERAL.UPSERT:
|
||||
commit('upsertPageBookmarkToList', data)
|
||||
break
|
||||
|
||||
case SyncEvents.GENERAL.DELETE:
|
||||
commit('removePageBookmarkFromList', data)
|
||||
break
|
||||
|
||||
case SyncEvents.GENERAL.DELETE_MULTIPLE:
|
||||
commit('removePageBookmarksFromList', data)
|
||||
break
|
||||
|
||||
default:
|
||||
console.error('search history: invalid sync event received')
|
||||
}
|
||||
})
|
||||
|
||||
ipcRenderer.on(IpcChannels.SYNC_PROFILES, (_, { event, data }) => {
|
||||
switch (event) {
|
||||
case SyncEvents.GENERAL.CREATE:
|
||||
|
@ -33,6 +33,7 @@ const state = {
|
||||
showProgressBar: false,
|
||||
showAddToPlaylistPrompt: false,
|
||||
showCreatePlaylistPrompt: false,
|
||||
showPageBookmarkPrompt: false,
|
||||
showSearchFilters: false,
|
||||
searchFilterValueChanged: false,
|
||||
progressBarPercentage: 0,
|
||||
@ -107,6 +108,10 @@ const getters = {
|
||||
return state.showCreatePlaylistPrompt
|
||||
},
|
||||
|
||||
getShowPageBookmarkPrompt(state) {
|
||||
return state.showPageBookmarkPrompt
|
||||
},
|
||||
|
||||
getShowSearchFilters(state) {
|
||||
return state.showSearchFilters
|
||||
},
|
||||
@ -384,6 +389,14 @@ const actions = {
|
||||
commit('setShowCreatePlaylistPrompt', false)
|
||||
},
|
||||
|
||||
showPageBookmarkPrompt ({ commit }) {
|
||||
commit('setShowPageBookmarkPrompt', true)
|
||||
},
|
||||
|
||||
hidePageBookmarkPrompt ({ commit }) {
|
||||
commit('setShowPageBookmarkPrompt', false)
|
||||
},
|
||||
|
||||
showSearchFilters ({ commit }) {
|
||||
commit('setShowSearchFilters', true)
|
||||
},
|
||||
@ -861,6 +874,10 @@ const mutations = {
|
||||
state.showCreatePlaylistPrompt = payload
|
||||
},
|
||||
|
||||
setShowPageBookmarkPrompt (state, payload) {
|
||||
state.showPageBookmarkPrompt = payload
|
||||
},
|
||||
|
||||
setShowSearchFilters (state, payload) {
|
||||
state.showSearchFilters = payload
|
||||
},
|
||||
|
@ -5,6 +5,7 @@ body {
|
||||
background-color: var(--bg-color);
|
||||
|
||||
--primary-input-color: rgb(0 0 0 / 50%);
|
||||
--side-nav-hover-text-color: var(--primary-text-color);
|
||||
--link-color: var(--accent-color);
|
||||
--link-visited-color: var(--accent-color-visited);
|
||||
--instance-menu-color: var(--search-bar-color);
|
||||
|
@ -686,20 +686,23 @@ Channel:
|
||||
Shorts:
|
||||
This channel does not currently have any shorts: ''
|
||||
Live:
|
||||
Live: ''
|
||||
This channel does not currently have any live streams: ''
|
||||
Live: 'Regstreeks'
|
||||
This channel does not currently have any live streams: 'Hierdie kanaal het tans
|
||||
geen rekstreekse stromings nie'
|
||||
Playlists:
|
||||
Playlists: ''
|
||||
This channel does not currently have any playlists: ''
|
||||
Playlists: 'Afspeellyste'
|
||||
This channel does not currently have any playlists: 'Hierdie kanaal het tans geen
|
||||
afspeellyste nie'
|
||||
Sort Types:
|
||||
Last Video Added: ''
|
||||
Newest: ''
|
||||
Oldest: ''
|
||||
Last Video Added: 'Laaste toegevoegde video'
|
||||
Newest: 'Nuutste'
|
||||
Oldest: 'Oudste'
|
||||
Podcasts:
|
||||
Podcasts: ''
|
||||
This channel does not currently have any podcasts: ''
|
||||
Podcasts: 'Podsendings'
|
||||
This channel does not currently have any podcasts: 'Hierdie kanaal het tans geen
|
||||
podsendings nie'
|
||||
Releases:
|
||||
Releases: ''
|
||||
Releases: 'Vrystellings'
|
||||
This channel does not currently have any releases: 'Hierdie kanaal het nie tans
|
||||
enige vrystellings nie'
|
||||
About:
|
||||
|
@ -32,6 +32,10 @@ Forward: Forward
|
||||
Open New Window: Open New Window
|
||||
Go to page: Go to {page}
|
||||
Close Banner: Close Banner
|
||||
Bookmark this page: Bookmark this page
|
||||
Edit bookmark for this page: Edit bookmark for this page
|
||||
Save: Save
|
||||
Name: Name
|
||||
|
||||
Version {versionNumber} is now available! Click for more details: Version {versionNumber} is now available! Click
|
||||
for more details
|
||||
@ -469,6 +473,10 @@ Settings:
|
||||
Remove All Playlists: Remove All Playlists
|
||||
All playlists have been removed: All playlists have been removed
|
||||
Are you sure you want to remove all your playlists?: Are you sure you want to remove all your playlists?
|
||||
Remove All Page Bookmarks: Remove All Page Bookmarks
|
||||
Are you sure you want to remove all page bookmarks? This cannot be undone.: Are
|
||||
you sure you want to remove all page bookmarks? This cannot be undone.
|
||||
All page bookmarks have been removed: All page bookmarks have been removed
|
||||
Subscription Settings:
|
||||
Subscription Settings: Subscription
|
||||
Hide Videos on Watch: Hide Videos on Watch
|
||||
@ -1080,6 +1088,18 @@ Hashtag:
|
||||
Hashtag: Hashtag
|
||||
This hashtag does not currently have any videos: This hashtag does not currently
|
||||
have any videos
|
||||
Page Bookmark:
|
||||
Bookmark this page: Bookmark this page
|
||||
Create Bookmark: Create Bookmark
|
||||
Edit Bookmark: Edit Bookmark
|
||||
Remove Bookmark: Remove Bookmark
|
||||
Created page bookmark: Created page bookmark "{name}"
|
||||
Updated page bookmark: Updated page bookmark "{name}"
|
||||
Removed page bookmark: Removed page bookmark "{name}"
|
||||
There is {count} other bookmark with the same name.: There is {count} other page bookmark with the same name. | There are {count} other page bookmarks with the same name.
|
||||
This page cannot be bookmarked.: This page cannot be bookmarked.
|
||||
Role Descriptions:
|
||||
bookmark: bookmark
|
||||
Moments Ago: moments ago
|
||||
Yes: Yes
|
||||
No: No
|
||||
|
Loading…
Reference in New Issue
Block a user