mirror of https://github.com/FreeTubeApp/FreeTube
Option to hide videos from certain channels (#2849)
* add logic to hide channels * Add new ft-input-tags ui element and use this for channel hiding * remove unused tooltip code * Add tooltip to the ft-input-tags and the new setting * Add spacer between toggle options and ft-flex-box * Swap to stringify from semicolon + add focus to label * Simplify the input_tags code + rename setting to channelsHidden * Fix issue shown by linter * Recentralize input button + fix tooltip for small windows * Improve accessiblity * fix hiding playlist when channel ID entered * pass tag directly to removeTag function Co-authored-by: petaded <code@zikl.co.uk> Co-authored-by: petaded <petaded@zikl.co.uk>
This commit is contained in:
parent
dbb54737c4
commit
f33f14268f
|
@ -2,12 +2,16 @@ import Vue from 'vue'
|
|||
import { mapActions } from 'vuex'
|
||||
import FtSettingsSection from '../ft-settings-section/ft-settings-section.vue'
|
||||
import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue'
|
||||
import FtInputTags from '../../components/ft-input-tags/ft-input-tags.vue'
|
||||
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'PlayerSettings',
|
||||
components: {
|
||||
'ft-settings-section': FtSettingsSection,
|
||||
'ft-toggle-switch': FtToggleSwitch
|
||||
'ft-toggle-switch': FtToggleSwitch,
|
||||
'ft-input-tags': FtInputTags,
|
||||
'ft-flex-box': FtFlexBox
|
||||
},
|
||||
computed: {
|
||||
hideVideoViews: function () {
|
||||
|
@ -60,6 +64,9 @@ export default Vue.extend({
|
|||
},
|
||||
hideChapters: function () {
|
||||
return this.$store.getters.getHideChapters
|
||||
},
|
||||
channelsHidden: function () {
|
||||
return JSON.parse(this.$store.getters.getChannelsHidden)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -70,6 +77,9 @@ export default Vue.extend({
|
|||
|
||||
this.updateHideRecommendedVideos(value)
|
||||
},
|
||||
handleChannelsHidden: function(value) {
|
||||
this.updateChannelsHidden(JSON.stringify(value))
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'updateHideVideoViews',
|
||||
|
@ -89,7 +99,8 @@ export default Vue.extend({
|
|||
'updateHideLiveStreams',
|
||||
'updateHideUpcomingPremieres',
|
||||
'updateHideSharingActions',
|
||||
'updateHideChapters'
|
||||
'updateHideChapters',
|
||||
'updateChannelsHidden'
|
||||
])
|
||||
}
|
||||
})
|
||||
|
|
|
@ -104,6 +104,17 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<ft-flex-box>
|
||||
<ft-input-tags
|
||||
:label="$t('Settings.Distraction Free Settings.Hide Channels')"
|
||||
:placeholder="$t('Settings.Distraction Free Settings.Hide Channels Placeholder')"
|
||||
:show-action-button="true"
|
||||
:tag-list="channelsHidden"
|
||||
:tooltip="$t('Tooltips.Distraction Free Settings.Hide Channels')"
|
||||
@change="handleChannelsHidden"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
</ft-settings-section>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
.ft-input-tags-component {
|
||||
position: relative;
|
||||
background-color: var(--bg-color);
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
display: block;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.ft-tag-box ul {
|
||||
overflow: auto;
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ft-tag-box li {
|
||||
list-style: none;
|
||||
background-color: var(--card-bg-color);
|
||||
margin: 5px;
|
||||
border-radius: 5px;
|
||||
display:flex;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.ft-tag-box li>label {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 10px;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
|
||||
.removeTagButton {
|
||||
color: var(--primary-text-color);
|
||||
opacity: 0.5;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.removeTagButton:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:deep(.ft-input-component .ft-input) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import Vue from 'vue'
|
||||
import FtInput from '../ft-input/ft-input.vue'
|
||||
import FtTooltip from '../ft-tooltip/ft-tooltip.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'FtInputTags',
|
||||
components: {
|
||||
'ft-input': FtInput,
|
||||
'ft-tooltip': FtTooltip
|
||||
},
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
showActionButton: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
tagList: {
|
||||
type: Array,
|
||||
default: () => { return [] }
|
||||
},
|
||||
tooltip: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateTags: function (text, e) {
|
||||
// text entered add tag and update tag list
|
||||
if (!this.tagList.includes(text.trim())) {
|
||||
const newList = this.tagList.slice(0)
|
||||
newList.push(text.trim())
|
||||
this.$emit('change', newList)
|
||||
}
|
||||
// clear input box
|
||||
this.$refs.childinput.handleClearTextClick()
|
||||
},
|
||||
removeTag: function (tag) {
|
||||
// Remove tag from list
|
||||
const tagName = tag.trim()
|
||||
if (this.tagList.includes(tagName)) {
|
||||
const newList = this.tagList.slice(0)
|
||||
const index = newList.indexOf(tagName)
|
||||
newList.splice(index, 1)
|
||||
this.$emit('change', newList)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div
|
||||
class="ft-input-tags-component"
|
||||
>
|
||||
<ft-input
|
||||
ref="childinput"
|
||||
:placeholder="placeholder"
|
||||
:label="label"
|
||||
:show-label="true"
|
||||
:tooltip="tooltip"
|
||||
:show-action-button="showActionButton"
|
||||
:select-on-focus="true"
|
||||
:force-action-button-icon-name="['fas', 'arrow-right']"
|
||||
@click="updateTags"
|
||||
/>
|
||||
|
||||
<div class="ft-tag-box">
|
||||
<ul>
|
||||
<li
|
||||
v-for="tag in tagList"
|
||||
:key="tag.id"
|
||||
>
|
||||
<label>{{ tag }}</label>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'fa-times']"
|
||||
class="removeTagButton"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
@click="removeTag(tag)"
|
||||
@keydown.enter.prevent="removeTag(tag)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./ft-input-tags.js" />
|
||||
<style scoped src="./ft-input-tags.css" />
|
|
@ -132,6 +132,12 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.inputAction.withLabel {
|
||||
/* If showLabel defined, re-centralize the input button*/
|
||||
top: 34px;
|
||||
}
|
||||
|
||||
|
||||
.search ::-webkit-calendar-picker-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,10 @@ export default Vue.extend({
|
|||
type: String,
|
||||
required: true
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
|
@ -25,6 +29,10 @@ export default Vue.extend({
|
|||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
forceActionButtonIconName: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
showClearTextButton: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
@ -59,6 +67,10 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
data: function () {
|
||||
let actionIcon = ['fas', 'search']
|
||||
if (this.forceActionButtonIconName !== null) {
|
||||
actionIcon = this.forceActionButtonIconName
|
||||
}
|
||||
return {
|
||||
id: '',
|
||||
inputData: '',
|
||||
|
@ -72,7 +84,7 @@ export default Vue.extend({
|
|||
// As the text input box should be empty
|
||||
clearTextButtonExisting: false,
|
||||
clearTextButtonVisible: false,
|
||||
actionButtonIconName: ['fas', 'search']
|
||||
actionButtonIconName: actionIcon
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -152,7 +164,7 @@ export default Vue.extend({
|
|||
// Only need to update icon if visible
|
||||
if (!this.showActionButton) { return }
|
||||
|
||||
if (!this.inputDataPresent) {
|
||||
if (!this.inputDataPresent && this.forceActionButtonIconName === null) {
|
||||
// Change back to default icon if text is blank
|
||||
this.actionButtonIconName = ['fas', 'search']
|
||||
return
|
||||
|
@ -180,18 +192,21 @@ export default Vue.extend({
|
|||
// isYoutubeLink is already `false`
|
||||
}
|
||||
}
|
||||
|
||||
if (isYoutubeLink) {
|
||||
// Go to URL (i.e. Video/Playlist/Channel
|
||||
this.actionButtonIconName = ['fas', 'arrow-right']
|
||||
} else {
|
||||
// Search with text
|
||||
this.actionButtonIconName = ['fas', 'search']
|
||||
if (this.forceActionButtonIconName === null) {
|
||||
if (isYoutubeLink) {
|
||||
// Go to URL (i.e. Video/Playlist/Channel
|
||||
this.actionButtonIconName = ['fas', 'arrow-right']
|
||||
} else {
|
||||
// Search with text
|
||||
this.actionButtonIconName = ['fas', 'search']
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (ex) {
|
||||
// On exception, consider text as invalid URL
|
||||
this.actionButtonIconName = ['fas', 'search']
|
||||
if (this.forceActionButtonIconName === null) {
|
||||
this.actionButtonIconName = ['fas', 'search']
|
||||
}
|
||||
// Rethrow exception
|
||||
throw ex
|
||||
}
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
<label
|
||||
v-if="showLabel"
|
||||
:for="id"
|
||||
class="selectLabel"
|
||||
>
|
||||
{{ placeholder }}
|
||||
{{ label || placeholder }}
|
||||
<ft-tooltip
|
||||
v-if="tooltip !== ''"
|
||||
class="selectTooltip"
|
||||
|
@ -57,7 +58,8 @@
|
|||
:icon="actionButtonIconName"
|
||||
class="inputAction"
|
||||
:class="{
|
||||
enabled: inputDataPresent
|
||||
enabled: inputDataPresent,
|
||||
withLabel: showLabel
|
||||
}"
|
||||
@click="handleClick"
|
||||
/>
|
||||
|
|
|
@ -37,6 +37,9 @@ export default Vue.extend({
|
|||
hideLiveStreams: function() {
|
||||
return this.$store.getters.getHideLiveStreams
|
||||
},
|
||||
channelsHidden: function() {
|
||||
return JSON.parse(this.$store.getters.getChannelsHidden)
|
||||
},
|
||||
hideUpcomingPremieres: function () {
|
||||
return this.$store.getters.getHideUpcomingPremieres
|
||||
}
|
||||
|
@ -46,6 +49,11 @@ export default Vue.extend({
|
|||
this.visible = visible
|
||||
},
|
||||
|
||||
/**
|
||||
* Show or Hide results in the list
|
||||
*
|
||||
* @return {bool} false to hide the video, true to show it
|
||||
*/
|
||||
showResult: function (data) {
|
||||
if (!data.type) {
|
||||
return false
|
||||
|
@ -66,8 +74,23 @@ export default Vue.extend({
|
|||
// hide upcoming
|
||||
return false
|
||||
}
|
||||
if (this.channelsHidden.includes(data.authorId) || this.channelsHidden.includes(data.author)) {
|
||||
// hide videos by author
|
||||
return false
|
||||
}
|
||||
} else if (data.type === 'channel') {
|
||||
if (this.channelsHidden.includes(data.channelID) || this.channelsHidden.includes(data.name)) {
|
||||
// hide channels by author
|
||||
return false
|
||||
}
|
||||
} else if (data.type === 'playlist') {
|
||||
if (this.channelsHidden.includes(data.authorId) || this.channelsHidden.includes(data.author)) {
|
||||
// hide playlists by author
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
|
|
@ -61,15 +61,15 @@
|
|||
justify-content: flex-start
|
||||
|
||||
@media only screen and (max-width: 680px)
|
||||
.settingsSection
|
||||
> div
|
||||
.settingsSection
|
||||
> div
|
||||
:deep(.text.bottom)
|
||||
left: -85px
|
||||
:deep(.switch-ctn.containsTooltip)
|
||||
left: -10px
|
||||
margin-right: 5px
|
||||
padding: 0px 10px 0px 10px
|
||||
:not(.select)
|
||||
:not(.select, .selectLabel)
|
||||
> :deep(.tooltip)
|
||||
display: inline-block
|
||||
position: absolute
|
||||
|
|
|
@ -195,6 +195,7 @@ const state = {
|
|||
hideChannelSubscriptions: false,
|
||||
hideCommentLikes: false,
|
||||
hideComments: false,
|
||||
channelsHidden: '[]',
|
||||
hideVideoDescription: false,
|
||||
hideLiveChat: false,
|
||||
hideLiveStreams: false,
|
||||
|
|
|
@ -326,6 +326,8 @@ Settings:
|
|||
Hide Upcoming Premieres: Hide Upcoming Premieres
|
||||
Hide Sharing Actions: Hide Sharing Actions
|
||||
Hide Chapters: Hide Chapters
|
||||
Hide Channels: Hide Videos From Channels
|
||||
Hide Channels Placeholder: Channel Name or ID
|
||||
Data Settings:
|
||||
Data Settings: Data Settings
|
||||
Select Import Type: Select Import Type
|
||||
|
@ -784,6 +786,9 @@ Tooltips:
|
|||
Custom External Player Arguments: Any custom command line arguments, separated by semicolons (';'),
|
||||
you want to be passed to the external player.
|
||||
DefaultCustomArgumentsTemplate: "(Default: '{defaultCustomArguments}')"
|
||||
Distraction Free Settings:
|
||||
Hide Channels: Enter a channel name or channel ID to hide all videos, playlists and the channel itself from appearing in search or trending.
|
||||
The channel name entered must be a complete match and is case sensitive.
|
||||
Subscription Settings:
|
||||
Fetch Feeds from RSS: When enabled, FreeTube will use RSS instead of its default
|
||||
method for grabbing your subscription feed. RSS is faster and prevents IP blocking,
|
||||
|
|
Loading…
Reference in New Issue