diff --git a/package.json b/package.json index b8e0a7294bddb..e0fe5fb099682 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "autolinker": "^4.0.0", "electron-context-menu": "^3.6.1", "lodash.debounce": "^4.0.8", - "marked": "^11.1.1", + "marked": "^11.2.0", "path-browserify": "^1.0.1", "process": "^0.11.10", "swiper": "^11.0.5", @@ -80,54 +80,54 @@ "youtubei.js": "^8.2.0" }, "devDependencies": { - "@babel/core": "^7.23.7", - "@babel/eslint-parser": "^7.23.3", + "@babel/core": "^7.23.9", + "@babel/eslint-parser": "^7.23.9", "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/preset-env": "^7.23.8", + "@babel/preset-env": "^7.23.9", "@double-great/stylelint-a11y": "^3.0.0", "babel-loader": "^9.1.3", - "copy-webpack-plugin": "^12.0.1", - "css-loader": "^6.9.0", - "css-minimizer-webpack-plugin": "^5.0.1", - "electron": "^28.1.3", + "copy-webpack-plugin": "^12.0.2", + "css-loader": "^6.9.1", + "css-minimizer-webpack-plugin": "^6.0.0", + "electron": "^28.2.0", "electron-builder": "^24.9.1", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsonc": "^2.11.2", + "eslint-plugin-jsonc": "^2.13.0", "eslint-plugin-n": "^16.6.2", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-unicorn": "^50.0.1", "eslint-plugin-vue": "^9.20.1", - "eslint-plugin-vuejs-accessibility": "^2.2.0", - "eslint-plugin-yml": "^1.11.0", + "eslint-plugin-vuejs-accessibility": "^2.2.1", + "eslint-plugin-yml": "^1.12.2", "html-webpack-plugin": "^5.6.0", "js-yaml": "^4.1.0", - "json-minimizer-webpack-plugin": "^4.0.0", - "lefthook": "^1.5.6", + "json-minimizer-webpack-plugin": "^5.0.0", + "lefthook": "^1.6.1", "mini-css-extract-plugin": "^2.7.7", "npm-run-all": "^4.1.5", "postcss": "^8.4.33", "postcss-scss": "^4.0.9", "prettier": "^2.8.8", "rimraf": "^5.0.5", - "sass": "^1.69.7", - "sass-loader": "^13.3.3", - "stylelint": "^16.1.0", + "sass": "^1.70.0", + "sass-loader": "^14.0.0", + "stylelint": "^16.2.0", "stylelint-config-sass-guidelines": "^11.0.0", "stylelint-config-standard": "^36.0.0", "stylelint-high-performance-animation": "^1.10.0", "stylelint-use-logical-spec": "^5.0.1", "tree-kill": "1.2.2", "vue-devtools": "^5.1.4", - "vue-eslint-parser": "^9.3.2", + "vue-eslint-parser": "^9.4.2", "vue-loader": "^15.10.0", - "webpack": "^5.89.0", + "webpack": "^5.90.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1", - "webpack-watch-external-files-plugin": "^2.0.0", + "webpack-watch-external-files-plugin": "^3.0.0", "yaml-eslint-parser": "^1.2.2" } } diff --git a/src/main/index.js b/src/main/index.js index 898f53258916f..23b561cee10b4 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -720,7 +720,8 @@ function runApp() { }) ipcMain.handle(IpcChannels.GET_SYSTEM_LOCALE, () => { - return app.getLocale() + // we should switch to getPreferredSystemLanguages at some point and iterate through until we find a supported locale + return app.getSystemLocale() }) ipcMain.handle(IpcChannels.GET_USER_DATA_PATH, () => { diff --git a/src/renderer/components/distraction-settings/distraction-settings.js b/src/renderer/components/distraction-settings/distraction-settings.js index c704206642e4f..a540b94bde48b 100644 --- a/src/renderer/components/distraction-settings/distraction-settings.js +++ b/src/renderer/components/distraction-settings/distraction-settings.js @@ -120,13 +120,16 @@ export default defineComponent({ return ch }) }, + forbiddenTitles: function() { + return JSON.parse(this.$store.getters.getForbiddenTitles) + }, hideSubscriptionsLiveTooltip: function () { return this.$t('Tooltips.Distraction Free Settings.Hide Subscriptions Live', { appWideSetting: this.$t('Settings.Distraction Free Settings.Hide Live Streams'), subsection: this.$t('Settings.Distraction Free Settings.Sections.General'), settingsSection: this.$t('Settings.Distraction Free Settings.Distraction Free Settings') }) - } + }, }, mounted: function () { this.verifyChannelsHidden() @@ -148,6 +151,9 @@ export default defineComponent({ handleChannelsHidden: function (value) { this.updateChannelsHidden(JSON.stringify(value)) }, + handleForbiddenTitles: function (value) { + this.updateForbiddenTitles(JSON.stringify(value)) + }, handleChannelsExists: function () { showToast(this.$t('Settings.Distraction Free Settings.Hide Channels Already Exists')) }, @@ -206,6 +212,7 @@ export default defineComponent({ 'updateHideSharingActions', 'updateHideChapters', 'updateChannelsHidden', + 'updateForbiddenTitles', 'updateShowDistractionFreeTitles', 'updateHideFeaturedChannels', 'updateHideChannelShorts', diff --git a/src/renderer/components/distraction-settings/distraction-settings.vue b/src/renderer/components/distraction-settings/distraction-settings.vue index eed6640160ff6..6420d447c8916 100644 --- a/src/renderer/components/distraction-settings/distraction-settings.vue +++ b/src/renderer/components/distraction-settings/distraction-settings.vue @@ -239,12 +239,24 @@ :tooltip="$t('Tooltips.Distraction Free Settings.Hide Channels')" :validate-tag-name="validateChannelId" :find-tag-info="findChannelTagInfo" + :are-channel-tags="true" @invalid-name="handleInvalidChannel" @error-find-tag-info="handleChannelAPIError" @change="handleChannelsHidden" @already-exists="handleChannelsExists" /> + + + diff --git a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css index 1a4878d64cb3c..701c0eb961aff 100644 --- a/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css +++ b/src/renderer/components/ft-channel-bubble/ft-channel-bubble.css @@ -25,6 +25,7 @@ block-size: 50px; border-radius: 100%; -webkit-border-radius: 100%; + object-fit: cover; } .selected { diff --git a/src/renderer/components/ft-community-post/ft-community-post.js b/src/renderer/components/ft-community-post/ft-community-post.js index f3a229c184a58..350eccc102020 100644 --- a/src/renderer/components/ft-community-post/ft-community-post.js +++ b/src/renderer/components/ft-community-post/ft-community-post.js @@ -25,6 +25,10 @@ export default defineComponent({ appearance: { type: String, required: true + }, + hideForbiddenTitles: { + type: Boolean, + default: true } }, data: function () { @@ -44,6 +48,15 @@ export default defineComponent({ computed: { listType: function () { return this.$store.getters.getListType + }, + + forbiddenTitles() { + if (!this.hideForbiddenTitles) { return [] } + return JSON.parse(this.$store.getters.getForbiddenTitles) + }, + + hideVideo() { + return this.forbiddenTitles.some((text) => this.data.postContent.content.title?.toLowerCase().includes(text.toLowerCase())) } }, created: function () { diff --git a/src/renderer/components/ft-community-post/ft-community-post.scss b/src/renderer/components/ft-community-post/ft-community-post.scss index b55876fe91a41..838cb658ba719 100644 --- a/src/renderer/components/ft-community-post/ft-community-post.scss +++ b/src/renderer/components/ft-community-post/ft-community-post.scss @@ -13,6 +13,12 @@ box-sizing: border-box; } +.hiddenVideo { + font-style: italic; + opacity: 0.85; + text-align: center; +} + .communityImage { block-size: 100%; inline-size: 100%; @@ -138,5 +144,5 @@ } .sliderContainer { - display: block; + display: grid; } diff --git a/src/renderer/components/ft-community-post/ft-community-post.vue b/src/renderer/components/ft-community-post/ft-community-post.vue index 34379ce287d82..053bd4c61a59d 100644 --- a/src/renderer/components/ft-community-post/ft-community-post.vue +++ b/src/renderer/components/ft-community-post/ft-community-post.vue @@ -56,27 +56,25 @@ class="postText" v-html="postText" /> -
- + - - - - -
+ +
@@ -90,9 +88,16 @@ v-if="type === 'video'" > +

+ {{ '[' + $t('Channel.Community.Video hidden by FreeTube') + ']' }} +

diff --git a/src/renderer/components/ft-input-tags/ft-input-tags.js b/src/renderer/components/ft-input-tags/ft-input-tags.js index 0c04f021be1f1..e139db3b76b9e 100644 --- a/src/renderer/components/ft-input-tags/ft-input-tags.js +++ b/src/renderer/components/ft-input-tags/ft-input-tags.js @@ -1,5 +1,6 @@ import { defineComponent } from 'vue' import FtInput from '../ft-input/ft-input.vue' +import { showToast } from '../../helpers/utils' export default defineComponent({ name: 'FtInputTags', @@ -7,6 +8,10 @@ export default defineComponent({ 'ft-input': FtInput, }, props: { + areChannelTags: { + type: Boolean, + default: false + }, disabled: { type: Boolean, default: false @@ -23,6 +28,10 @@ export default defineComponent({ type: String, required: true }, + minInputLength: { + type: Number, + default: 1 + }, showActionButton: { type: Boolean, default: true @@ -46,6 +55,30 @@ export default defineComponent({ }, methods: { updateTags: async function (text, _e) { + if (this.areChannelTags) { + await this.updateChannelTags(text, _e) + return + } + // add tag and update tag list + const trimmedText = text.trim() + + if (this.minInputLength > trimmedText.length) { + showToast(this.$tc('Trimmed input must be at least N characters long', this.minInputLength, { length: this.minInputLength })) + return + } + + if (this.tagList.includes(trimmedText)) { + showToast(this.$t('Tag already exists', { tagName: trimmedText })) + return + } + + const newList = this.tagList.slice(0) + newList.push(trimmedText) + this.$emit('change', newList) + // clear input box + this.$refs.tagNameInput.handleClearTextClick() + }, + updateChannelTags: async function (text, _e) { // get text without spaces after last '/' in url, if any const name = text.split('/').pop().trim() @@ -73,6 +106,20 @@ export default defineComponent({ this.$refs.tagNameInput.handleClearTextClick() }, removeTag: function (tag) { + if (this.areChannelTags) { + this.removeChannelTag(tag) + return + } + // 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) + } + }, + removeChannelTag: function (tag) { // Remove tag from list if (this.tagList.some((tmpTag) => tmpTag.name === tag.name)) { const newList = this.tagList.filter((tmpTag) => tmpTag.name !== tag.name) diff --git a/src/renderer/components/ft-input-tags/ft-input-tags.vue b/src/renderer/components/ft-input-tags/ft-input-tags.vue index 6b236e937ca77..58464b5476cd4 100644 --- a/src/renderer/components/ft-input-tags/ft-input-tags.vue +++ b/src/renderer/components/ft-input-tags/ft-input-tags.vue @@ -13,6 +13,7 @@ :disabled="disabled" :placeholder="tagNamePlaceholder" :label="label" + :min-input-length="minInputLength" :show-label="true" :tooltip="tooltip" :show-action-button="showActionButton" @@ -26,18 +27,21 @@ v-for="tag in tagList" :key="tag.id" > - - + - - {{ (tag.preferredName) ? tag.preferredName : tag.name }} + + + {{ (tag.preferredName) ? tag.preferredName : tag.name }} + + {{ tag }} this.data.title?.toLowerCase().includes(text.toLowerCase()))) { + return false + } } else if (dataType === 'channel') { const attrsToCheck = [ // Local API @@ -117,6 +128,9 @@ export default defineComponent({ return false } } else if (dataType === 'playlist') { + if (this.forbiddenTitles.some((text) => this.data.title?.toLowerCase().includes(text.toLowerCase()))) { + return false + } const attrsToCheck = [ // Local API data.channelId, diff --git a/src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.vue b/src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.vue index 1cace8c779682..654d6bbde5aa8 100644 --- a/src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.vue +++ b/src/renderer/components/ft-list-lazy-wrapper/ft-list-lazy-wrapper.vue @@ -31,6 +31,7 @@ /> diff --git a/src/renderer/components/ft-list-video-lazy/ft-list-video-lazy.js b/src/renderer/components/ft-list-video-lazy/ft-list-video-lazy.js index ef5a044b27d44..5fb6ba9f8604f 100644 --- a/src/renderer/components/ft-list-video-lazy/ft-list-video-lazy.js +++ b/src/renderer/components/ft-list-video-lazy/ft-list-video-lazy.js @@ -75,10 +75,15 @@ export default defineComponent({ type: Boolean, default: false, }, + hideForbiddenTitles: { + type: Boolean, + default: true + } }, data: function () { return { - visible: false + visible: false, + display: 'block' } }, computed: { @@ -95,9 +100,15 @@ export default defineComponent({ }) }, + forbiddenTitles() { + if (!this.hideForbiddenTitles) { return [] } + return JSON.parse(this.$store.getters.getForbiddenTitles) + }, + shouldBeVisible() { return !(this.channelsHidden.some(ch => ch.name === this.data.authorId) || - this.channelsHidden.some(ch => ch.name === this.data.author)) + this.channelsHidden.some(ch => ch.name === this.data.author) || + this.forbiddenTitles.some((text) => this.data.title?.toLowerCase().includes(text.toLowerCase()))) } }, created() { @@ -107,6 +118,8 @@ export default defineComponent({ onVisibilityChanged: function (visible) { if (visible && this.shouldBeVisible) { this.visible = visible + } else if (visible) { + this.display = 'none' } } } diff --git a/src/renderer/components/ft-list-video-lazy/ft-list-video-lazy.vue b/src/renderer/components/ft-list-video-lazy/ft-list-video-lazy.vue index 9af01de8af16a..69abd6a3a9221 100644 --- a/src/renderer/components/ft-list-video-lazy/ft-list-video-lazy.vue +++ b/src/renderer/components/ft-list-video-lazy/ft-list-video-lazy.vue @@ -4,6 +4,7 @@ callback: onVisibilityChanged, once: true, }" + :style="{ display }" > { + // Legacy support + if (typeof ch === 'string') { + return { name: ch, preferredName: '', icon: '' } + } + return ch + }) + }, + + // As we only use this component in Playlist and watch-video-playlist, + // where title filtering is never desired, we don't have any title filtering logic here, + // like we do in ft-list-video-lazy + + shouldBeVisible() { + return !(this.channelsHidden.some(ch => ch.name === this.data.authorId) || + this.channelsHidden.some(ch => ch.name === this.data.author)) + } + }, + created() { + this.visible = this.initialVisibleState + }, + methods: { + onVisibilityChanged: function (visible) { + if (visible && this.shouldBeVisible) { + this.visible = visible + } else if (visible) { + this.show = false + } + } + } +}) diff --git a/src/renderer/components/ft-list-video-numbered/ft-list-video-numbered.vue b/src/renderer/components/ft-list-video-numbered/ft-list-video-numbered.vue new file mode 100644 index 0000000000000..745a704fe84c5 --- /dev/null +++ b/src/renderer/components/ft-list-video-numbered/ft-list-video-numbered.vue @@ -0,0 +1,53 @@ + + +