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:
petaded 2022-12-17 15:49:44 +00:00 committed by GitHub
parent dbb54737c4
commit f33f14268f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 235 additions and 17 deletions

View File

@ -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'
])
}
})

View File

@ -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>

View File

@ -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;
}

View File

@ -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)
}
}
}
})

View File

@ -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" />

View File

@ -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;
}

View File

@ -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
}

View File

@ -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"
/>

View File

@ -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
}
}
})

View File

@ -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

View File

@ -195,6 +195,7 @@ const state = {
hideChannelSubscriptions: false,
hideCommentLikes: false,
hideComments: false,
channelsHidden: '[]',
hideVideoDescription: false,
hideLiveChat: false,
hideLiveStreams: false,

View File

@ -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,