Skip to content

Commit 64e3f32

Browse files
authored
Video title filter / blacklist (#4202)
* Implement hiding of videos with user-inputted text * Implement ft-input minInputLength * Enable for playlists * Enable feature on channel pages The premise for this change is that users would not want to see that forbidden content anywhere, as opposed to hidden channels, where you're clearly on a channel page to see things from that channel. * Fix 'Play Next Video' playing forbiddenTitle videos and hidden channel videos * Fix issue of hidden recommended videos taking up vertical space * Rename variables to better match non-video-specific function, and remove blocks from History and videos in playlists * Fix to respect hideForbiddenTitles value * Modify label * Clarify restriction affecting original titles * Add toast for entered input of length below min input length * Add toast for element already exists * Update to not clear if duplicate tag is entered for Hide Forbidden feature
1 parent ea3db79 commit 64e3f32

File tree

21 files changed

+193
-18
lines changed

21 files changed

+193
-18
lines changed

src/renderer/components/distraction-settings/distraction-settings.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,16 @@ export default defineComponent({
120120
return ch
121121
})
122122
},
123+
forbiddenTitles: function() {
124+
return JSON.parse(this.$store.getters.getForbiddenTitles)
125+
},
123126
hideSubscriptionsLiveTooltip: function () {
124127
return this.$t('Tooltips.Distraction Free Settings.Hide Subscriptions Live', {
125128
appWideSetting: this.$t('Settings.Distraction Free Settings.Hide Live Streams'),
126129
subsection: this.$t('Settings.Distraction Free Settings.Sections.General'),
127130
settingsSection: this.$t('Settings.Distraction Free Settings.Distraction Free Settings')
128131
})
129-
}
132+
},
130133
},
131134
mounted: function () {
132135
this.verifyChannelsHidden()
@@ -148,6 +151,9 @@ export default defineComponent({
148151
handleChannelsHidden: function (value) {
149152
this.updateChannelsHidden(JSON.stringify(value))
150153
},
154+
handleForbiddenTitles: function (value) {
155+
this.updateForbiddenTitles(JSON.stringify(value))
156+
},
151157
handleChannelsExists: function () {
152158
showToast(this.$t('Settings.Distraction Free Settings.Hide Channels Already Exists'))
153159
},
@@ -206,6 +212,7 @@ export default defineComponent({
206212
'updateHideSharingActions',
207213
'updateHideChapters',
208214
'updateChannelsHidden',
215+
'updateForbiddenTitles',
209216
'updateShowDistractionFreeTitles',
210217
'updateHideFeaturedChannels',
211218
'updateHideChannelShorts',

src/renderer/components/distraction-settings/distraction-settings.vue

+12
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,24 @@
239239
:tooltip="$t('Tooltips.Distraction Free Settings.Hide Channels')"
240240
:validate-tag-name="validateChannelId"
241241
:find-tag-info="findChannelTagInfo"
242+
:are-channel-tags="true"
242243
@invalid-name="handleInvalidChannel"
243244
@error-find-tag-info="handleChannelAPIError"
244245
@change="handleChannelsHidden"
245246
@already-exists="handleChannelsExists"
246247
/>
247248
</ft-flex-box>
249+
<ft-flex-box>
250+
<ft-input-tags
251+
:label="$t('Settings.Distraction Free Settings.Hide Videos and Playlists Containing Text')"
252+
:tag-name-placeholder="$t('Settings.Distraction Free Settings.Hide Videos and Playlists Containing Text Placeholder')"
253+
:show-action-button="true"
254+
:tag-list="forbiddenTitles"
255+
:min-input-length="3"
256+
:tooltip="$t('Tooltips.Distraction Free Settings.Hide Videos and Playlists Containing Text')"
257+
@change="handleForbiddenTitles"
258+
/>
259+
</ft-flex-box>
248260
</ft-settings-section>
249261
</template>
250262

src/renderer/components/ft-community-post/ft-community-post.js

+13
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export default defineComponent({
2525
appearance: {
2626
type: String,
2727
required: true
28+
},
29+
hideForbiddenTitles: {
30+
type: Boolean,
31+
default: true
2832
}
2933
},
3034
data: function () {
@@ -44,6 +48,15 @@ export default defineComponent({
4448
computed: {
4549
listType: function () {
4650
return this.$store.getters.getListType
51+
},
52+
53+
forbiddenTitles() {
54+
if (!this.hideForbiddenTitles) { return [] }
55+
return JSON.parse(this.$store.getters.getForbiddenTitles)
56+
},
57+
58+
hideVideo() {
59+
return this.forbiddenTitles.some((text) => this.data.postContent.content.title?.toLowerCase().includes(text.toLowerCase()))
4760
}
4861
},
4962
created: function () {

src/renderer/components/ft-community-post/ft-community-post.scss

+6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
box-sizing: border-box;
1414
}
1515

16+
.hiddenVideo {
17+
font-style: italic;
18+
opacity: 0.85;
19+
text-align: center;
20+
}
21+
1622
.communityImage {
1723
block-size: 100%;
1824
inline-size: 100%;

src/renderer/components/ft-community-post/ft-community-post.vue

+7
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,16 @@
9090
v-if="type === 'video'"
9191
>
9292
<ft-list-video
93+
v-if="!hideVideo"
9394
:data="data.postContent.content"
9495
appearance=""
9596
/>
97+
<p
98+
v-else
99+
class="hiddenVideo"
100+
>
101+
{{ '[' + $t('Channel.Community.Video hidden by FreeTube') + ']' }}
102+
</p>
96103
</div>
97104
<div
98105
v-if="type === 'poll' || type === 'quiz'"

src/renderer/components/ft-element-list/ft-element-list.js

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ export default defineComponent({
3030
type: Boolean,
3131
default: true,
3232
},
33+
hideForbiddenTitles: {
34+
type: Boolean,
35+
default: true
36+
}
3337
},
3438
computed: {
3539
listType: function () {

src/renderer/components/ft-element-list/ft-element-list.vue

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
:layout="displayValue"
1313
:show-video-with-last-viewed-playlist="showVideoWithLastViewedPlaylist"
1414
:use-channels-hidden-preference="useChannelsHiddenPreference"
15+
:hide-forbidden-titles="hideForbiddenTitles"
1516
/>
1617
</ft-auto-grid>
1718
</template>

src/renderer/components/ft-input-tags/ft-input-tags.js

+47
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { defineComponent } from 'vue'
22
import FtInput from '../ft-input/ft-input.vue'
3+
import { showToast } from '../../helpers/utils'
34

45
export default defineComponent({
56
name: 'FtInputTags',
67
components: {
78
'ft-input': FtInput,
89
},
910
props: {
11+
areChannelTags: {
12+
type: Boolean,
13+
default: false
14+
},
1015
disabled: {
1116
type: Boolean,
1217
default: false
@@ -23,6 +28,10 @@ export default defineComponent({
2328
type: String,
2429
required: true
2530
},
31+
minInputLength: {
32+
type: Number,
33+
default: 1
34+
},
2635
showActionButton: {
2736
type: Boolean,
2837
default: true
@@ -46,6 +55,30 @@ export default defineComponent({
4655
},
4756
methods: {
4857
updateTags: async function (text, _e) {
58+
if (this.areChannelTags) {
59+
await this.updateChannelTags(text, _e)
60+
return
61+
}
62+
// add tag and update tag list
63+
const trimmedText = text.trim()
64+
65+
if (this.minInputLength > trimmedText.length) {
66+
showToast(this.$tc('Trimmed input must be at least N characters long', this.minInputLength, { length: this.minInputLength }))
67+
return
68+
}
69+
70+
if (this.tagList.includes(trimmedText)) {
71+
showToast(this.$t('Tag already exists', { tagName: trimmedText }))
72+
return
73+
}
74+
75+
const newList = this.tagList.slice(0)
76+
newList.push(trimmedText)
77+
this.$emit('change', newList)
78+
// clear input box
79+
this.$refs.tagNameInput.handleClearTextClick()
80+
},
81+
updateChannelTags: async function (text, _e) {
4982
// get text without spaces after last '/' in url, if any
5083
const name = text.split('/').pop().trim()
5184

@@ -73,6 +106,20 @@ export default defineComponent({
73106
this.$refs.tagNameInput.handleClearTextClick()
74107
},
75108
removeTag: function (tag) {
109+
if (this.areChannelTags) {
110+
this.removeChannelTag(tag)
111+
return
112+
}
113+
// Remove tag from list
114+
const tagName = tag.trim()
115+
if (this.tagList.includes(tagName)) {
116+
const newList = this.tagList.slice(0)
117+
const index = newList.indexOf(tagName)
118+
newList.splice(index, 1)
119+
this.$emit('change', newList)
120+
}
121+
},
122+
removeChannelTag: function (tag) {
76123
// Remove tag from list
77124
if (this.tagList.some((tmpTag) => tmpTag.name === tag.name)) {
78125
const newList = this.tagList.filter((tmpTag) => tmpTag.name !== tag.name)

src/renderer/components/ft-input-tags/ft-input-tags.vue

+15-11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
:disabled="disabled"
1414
:placeholder="tagNamePlaceholder"
1515
:label="label"
16+
:min-input-length="minInputLength"
1617
:show-label="true"
1718
:tooltip="tooltip"
1819
:show-action-button="showActionButton"
@@ -26,18 +27,21 @@
2627
v-for="tag in tagList"
2728
:key="tag.id"
2829
>
29-
<router-link
30-
v-if="tag.icon"
31-
:to="tag.iconHref ?? ''"
32-
class="tag-icon-link"
33-
>
34-
<img
35-
:src="tag.icon"
36-
alt=""
37-
class="tag-icon"
30+
<template v-if="areChannelTags">
31+
<router-link
32+
v-if="tag.icon"
33+
:to="tag.iconHref ?? ''"
34+
class="tag-icon-link"
3835
>
39-
</router-link>
40-
<span>{{ (tag.preferredName) ? tag.preferredName : tag.name }}</span>
36+
<img
37+
:src="tag.icon"
38+
alt=""
39+
class="tag-icon"
40+
>
41+
</router-link>
42+
<span>{{ (tag.preferredName) ? tag.preferredName : tag.name }}</span>
43+
</template>
44+
<span v-else>{{ tag }}</span>
4145
<font-awesome-icon
4246
v-if="!disabled"
4347
:icon="['fas', 'fa-times']"

src/renderer/components/ft-input/ft-input.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { defineComponent } from 'vue'
2-
import FtTooltip from '../ft-tooltip/ft-tooltip.vue'
32
import { mapActions } from 'vuex'
3+
4+
import FtTooltip from '../ft-tooltip/ft-tooltip.vue'
45
import { isKeyboardEventKeyPrintableChar, isNullOrEmpty } from '../../helpers/strings'
56

67
export default defineComponent({
@@ -143,7 +144,9 @@ export default defineComponent({
143144
methods: {
144145
handleClick: function (e) {
145146
// No action if no input text
146-
if (!this.inputDataPresent) { return }
147+
if (!this.inputDataPresent) {
148+
return
149+
}
147150

148151
this.searchState.showOptions = false
149152
this.searchState.selectedOption = -1

src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.js

+14
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export default defineComponent({
4343
type: Boolean,
4444
default: true,
4545
},
46+
hideForbiddenTitles: {
47+
type: Boolean,
48+
default: true
49+
},
4650
},
4751
data: function () {
4852
return {
@@ -65,6 +69,10 @@ export default defineComponent({
6569
return ch
6670
})
6771
},
72+
forbiddenTitles: function() {
73+
if (!this.hideForbiddenTitles) { return [] }
74+
return JSON.parse(this.$store.getters.getForbiddenTitles)
75+
},
6876
hideUpcomingPremieres: function () {
6977
return this.$store.getters.getHideUpcomingPremieres
7078
},
@@ -102,6 +110,9 @@ export default defineComponent({
102110
// hide videos by author
103111
return false
104112
}
113+
if (this.forbiddenTitles.some((text) => this.data.title?.toLowerCase().includes(text.toLowerCase()))) {
114+
return false
115+
}
105116
} else if (dataType === 'channel') {
106117
const attrsToCheck = [
107118
// Local API
@@ -117,6 +128,9 @@ export default defineComponent({
117128
return false
118129
}
119130
} else if (dataType === 'playlist') {
131+
if (this.forbiddenTitles.some((text) => this.data.title?.toLowerCase().includes(text.toLowerCase()))) {
132+
return false
133+
}
120134
const attrsToCheck = [
121135
// Local API
122136
data.channelId,

src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.vue

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
/>
3232
<ft-community-post
3333
v-else-if="finalDataType === 'community'"
34+
:hide-forbidden-titles="hideForbiddenTitles"
3435
:appearance="appearance"
3536
:data="data"
3637
/>

src/renderer/components/ft-list-video-lazy/ft-list-video-lazy.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,15 @@ export default defineComponent({
7575
type: Boolean,
7676
default: false,
7777
},
78+
hideForbiddenTitles: {
79+
type: Boolean,
80+
default: true
81+
}
7882
},
7983
data: function () {
8084
return {
81-
visible: false
85+
visible: false,
86+
display: 'block'
8287
}
8388
},
8489
computed: {
@@ -95,9 +100,15 @@ export default defineComponent({
95100
})
96101
},
97102

103+
forbiddenTitles() {
104+
if (!this.hideForbiddenTitles) { return [] }
105+
return JSON.parse(this.$store.getters.getForbiddenTitles)
106+
},
107+
98108
shouldBeVisible() {
99109
return !(this.channelsHidden.some(ch => ch.name === this.data.authorId) ||
100-
this.channelsHidden.some(ch => ch.name === this.data.author))
110+
this.channelsHidden.some(ch => ch.name === this.data.author) ||
111+
this.forbiddenTitles.some((text) => this.data.title?.toLowerCase().includes(text.toLowerCase())))
101112
}
102113
},
103114
created() {
@@ -107,6 +118,8 @@ export default defineComponent({
107118
onVisibilityChanged: function (visible) {
108119
if (visible && this.shouldBeVisible) {
109120
this.visible = visible
121+
} else if (visible) {
122+
this.display = 'none'
110123
}
111124
}
112125
}

src/renderer/components/ft-list-video-lazy/ft-list-video-lazy.vue

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
callback: onVisibilityChanged,
55
once: true,
66
}"
7+
:style="{ display }"
78
>
89
<ft-list-video
910
v-if="visible"

src/renderer/components/watch-video-playlist/watch-video-playlist.vue

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
:playlist-reverse="reversePlaylist"
145145
:playlist-shuffle="shuffleEnabled"
146146
:playlist-loop="loopEnabled"
147+
:hide-forbidden-titles="false"
147148
appearance="watchPlaylistItem"
148149
force-list-type="list"
149150
:initial-visible-state="index < (currentVideoIndexZeroBased + 4) && index > (currentVideoIndexZeroBased - 4)"

src/renderer/store/modules/settings.js

+1
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ const state = {
206206
hideComments: false,
207207
hideFeaturedChannels: false,
208208
channelsHidden: '[]',
209+
forbiddenTitles: '[]',
209210
hideVideoDescription: false,
210211
hideLiveChat: false,
211212
hideLiveStreams: false,

0 commit comments

Comments
 (0)