Skip to content

Commit 1be051e

Browse files
committed
Merge branch 'development' into feature/history/remember-search-query
* development: (35 commits) Translated using Weblate (Hungarian) Translated using Weblate (French) Translated using Weblate (Chinese (Simplified Han script)) Translated using Weblate (Serbian) Translated using Weblate (Italian) Translated using Weblate (English (United Kingdom)) Translated using Weblate (French) Translated using Weblate (Czech) Translated using Weblate (Bulgarian) Translated using Weblate (Italian) Translated using Weblate (Turkish) Translated using Weblate (German) Translated using Weblate (Spanish) Add Playlist Sort By Video Duration (FreeTubeApp#5627) Translated using Weblate (Estonian) Translated using Weblate (Italian) Translated using Weblate (French) Translated using Weblate (Chinese (Simplified Han script)) Translated using Weblate (Serbian) Translated using Weblate (Serbian) ...
2 parents 707f950 + bc80ec2 commit 1be051e

31 files changed

+412
-87
lines changed

_scripts/ProcessLocalesPlugin.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class ProcessLocalesPlugin {
7272
}
7373

7474
for (let [locale, data] of this.locales) {
75+
// eslint-disable-next-line no-async-promise-executor
7576
promises.push(new Promise(async (resolve) => {
7677
if (IS_DEV_SERVER && compiler.fileTimestamps) {
7778
const filePath = join(this.inputDir, `${locale}.yaml`)
@@ -131,6 +132,7 @@ class ProcessLocalesPlugin {
131132
})
132133

133134
compiler.hooks.afterCompile.tap(PLUGIN_NAME, (compilation) => {
135+
// eslint-disable-next-line no-extra-boolean-cast
134136
if (!!compiler.watching) {
135137
// watch locale files for changes
136138
compilation.fileDependencies.addAll(this.filePaths)

_scripts/getShakaLocales.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const { readFileSync, readdirSync } = require('fs')
33
function getPreloadedLocales() {
44
const localesFile = readFileSync(`${__dirname}/../node_modules/shaka-player/dist/locales.js`, 'utf-8')
55

6-
const localesLine = localesFile.match(/^\/\/ LOCALES: ([\w, -]+)$/m)
6+
const localesLine = localesFile.match(/^\/\/ LOCALES: ([\w ,-]+)$/m)
77

88
if (!localesLine) {
99
throw new Error("Failed to parse shaka-player's preloaded locales")

_scripts/patchShaka.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ async function removeRobotoFont() {
6363
let cssContents = readFileSync(cssFileHandle, 'utf-8')
6464

6565
const beforeReplacement = cssContents.length
66-
cssContents = cssContents.replace(/@font-face\{font-family:Roboto;[^}]+\}/, '')
66+
cssContents = cssContents.replace(/@font-face{font-family:Roboto;[^}]+}/, '')
6767

6868
if (cssContents.length !== beforeReplacement) {
6969
ftruncateSync(cssFileHandle)
@@ -100,7 +100,7 @@ async function replaceAndDownloadMaterialIconsFont() {
100100
let newFontCSS = text.match(/(@font-face\s*{[^}]+})/)[1].replaceAll('\n', '')
101101

102102

103-
const urlMatch = newFontCSS.match(/https:\/\/fonts\.gstatic\.com\/s\/materialiconsround\/(?<version>[^\/]+)\/[^.]+\.(?<extension>[\w]+)/)
103+
const urlMatch = newFontCSS.match(/https:\/\/fonts\.gstatic\.com\/s\/materialiconsround\/(?<version>[^/]+)\/[^.]+\.(?<extension>\w+)/)
104104

105105
const url = urlMatch[0]
106106
const { version, extension } = urlMatch.groups

eslint.config.mjs

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ const compat = new FlatCompat({
2222
})
2323

2424
export default [
25+
{
26+
ignores: [
27+
'dist/',
28+
'eslint.config.mjs'
29+
]
30+
},
2531
...fixupConfigRules(
2632
compat.config({
2733
extends: ['standard']
@@ -37,9 +43,7 @@ export default [
3743
'**/*.{js,vue}',
3844
],
3945
ignores: [
40-
'**/node_modules',
41-
'**/_scripts',
42-
'**/dist',
46+
'_scripts/',
4347
],
4448
plugins: {
4549
unicorn: eslintPluginUnicorn,
@@ -125,9 +129,7 @@ export default [
125129
{
126130
files: ['**/*.json'],
127131
ignores: [
128-
'**/node_modules/**',
129-
'**/_scripts/**',
130-
'**/dist/**',
132+
'_scripts/',
131133
],
132134

133135
languageOptions: {
@@ -152,10 +154,8 @@ export default [
152154
{
153155
files: ['**/*.{yml,yaml}'],
154156
ignores: [
155-
'**/node_modules/**',
156-
'**/_scripts/**',
157-
'**/dist/**',
158-
'**/.github/**',
157+
'.github/',
158+
'_scripts/'
159159
],
160160

161161
languageOptions: {
@@ -192,4 +192,43 @@ export default [
192192
},
193193
},
194194
},
195+
{
196+
files: ['_scripts/*.js'],
197+
languageOptions: {
198+
globals: {
199+
...globals.node
200+
},
201+
ecmaVersion: 'latest',
202+
},
203+
204+
plugins: {
205+
unicorn: eslintPluginUnicorn,
206+
},
207+
208+
rules: {
209+
'no-console': 'off',
210+
'n/no-path-concat': 'off',
211+
'unicorn/better-regex': 'error',
212+
}
213+
},
214+
{
215+
files: ['_scripts/*.mjs'],
216+
languageOptions: {
217+
globals: {
218+
...globals.node,
219+
},
220+
ecmaVersion: 'latest',
221+
sourceType: 'module',
222+
},
223+
224+
plugins: {
225+
unicorn: eslintPluginUnicorn,
226+
},
227+
228+
rules: {
229+
'no-console': 'off',
230+
'n/no-path-concat': 'off',
231+
'unicorn/better-regex': 'error',
232+
}
233+
}
195234
]

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
"lint-all": "run-p lint lint-json",
3838
"lint": "run-p eslint-lint lint-style",
3939
"lint-fix": "run-p eslint-lint-fix lint-style-fix",
40-
"eslint-lint": "eslint --config eslint.config.mjs \"./src/**/*.js\" \"./src/**/*.vue\" \"./static/**/*.js\"",
41-
"eslint-lint-fix": "eslint --config eslint.config.mjs --fix \"./src/**/*.js\" \"./src/**/*.vue\" \"./static/**/*.js\"",
40+
"eslint-lint": "eslint --config eslint.config.mjs \"./src/**/*.js\" \"./src/**/*.vue\" \"./static/**/*.js\" \"./_scripts/*.js\" \"./_scripts/*.mjs\"",
41+
"eslint-lint-fix": "eslint --config eslint.config.mjs --fix \"./src/**/*.js\" \"./src/**/*.vue\" \"./static/**/*.js\" \"./_scripts/*.js\" \"./_scripts/*.mjs\"",
4242
"lint-json": "eslint --config eslint.config.mjs \"./static/**/*.json\"",
4343
"lint-style": "stylelint \"**/*.{css,scss}\"",
4444
"lint-style-fix": "stylelint --fix \"**/*.{css,scss}\"",

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

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,7 @@ export default defineComponent({
875875
// to the app, so we'll only grab the data we need here.
876876

877877
const playlistObject = {}
878+
const videoIdToBeAddedSet = new Set()
878879

879880
Object.keys(playlistData).forEach((key) => {
880881
if ([requiredKeys, optionalKeys, ignoredKeys].every((ks) => !ks.includes(key))) {
@@ -888,6 +889,7 @@ export default defineComponent({
888889

889890
if (videoObjectHasAllRequiredKeys) {
890891
videoArray.push(video)
892+
videoIdToBeAddedSet.add(video.videoId)
891893
}
892894
})
893895

@@ -901,48 +903,62 @@ export default defineComponent({
901903
const playlistObjectKeys = Object.keys(playlistObject)
902904
const playlistObjectHasAllRequiredKeys = requiredKeys.every((k) => playlistObjectKeys.includes(k))
903905

904-
if (playlistObjectHasAllRequiredKeys) {
905-
const existingPlaylist = this.allPlaylists.find((playlist) => {
906-
return playlist.playlistName === playlistObject.playlistName
907-
})
906+
if (!playlistObjectHasAllRequiredKeys) {
907+
const message = this.$t('Settings.Data Settings.Playlist insufficient data', { playlist: playlistData.playlistName })
908+
showToast(message)
909+
return
910+
}
908911

909-
if (existingPlaylist !== undefined) {
910-
playlistObject.videos.forEach((video) => {
911-
let videoExists = false
912-
if (video.playlistItemId != null) {
913-
// Find by `playlistItemId` if present
914-
videoExists = existingPlaylist.videos.some((x) => {
915-
// Allow duplicate (by videoId) videos to be added
916-
return x.videoId === video.videoId && x.playlistItemId === video.playlistItemId
917-
})
918-
} else {
919-
// Older playlist exports have no `playlistItemId` but have `timeAdded`
920-
// Which might be duplicate for copied playlists with duplicate `videoId`
921-
videoExists = existingPlaylist.videos.some((x) => {
922-
// Allow duplicate (by videoId) videos to be added
923-
return x.videoId === video.videoId && x.timeAdded === video.timeAdded
924-
})
925-
}
912+
const existingPlaylist = this.allPlaylists.find((playlist) => {
913+
return playlist.playlistName === playlistObject.playlistName
914+
})
926915

927-
if (!videoExists) {
928-
// Keep original `timeAdded` value
929-
const payload = {
930-
_id: existingPlaylist._id,
931-
videoData: video,
932-
}
916+
if (existingPlaylist === undefined) {
917+
this.addPlaylist(playlistObject)
918+
return
919+
}
933920

934-
this.addVideo(payload)
935-
}
936-
})
937-
// Update playlist's `lastUpdatedAt`
938-
this.updatePlaylist({ _id: existingPlaylist._id })
921+
const duplicateVideoPresentInToBeAdded = playlistObject.videos.length > videoIdToBeAddedSet.size
922+
const existingVideoIdSet = existingPlaylist.videos.reduce((video) => videoIdToBeAddedSet.add(video.videoId), new Set())
923+
const duplicateVideoPresentInExistingPlaylist = existingPlaylist.videos.length > existingVideoIdSet.size
924+
const shouldAddDuplicateVideos = duplicateVideoPresentInToBeAdded || duplicateVideoPresentInExistingPlaylist
925+
926+
playlistObject.videos.forEach((video) => {
927+
let videoExists = false
928+
if (shouldAddDuplicateVideos) {
929+
if (video.playlistItemId != null) {
930+
// Find by `playlistItemId` if present
931+
videoExists = existingPlaylist.videos.some((x) => {
932+
// Allow duplicate (by videoId) videos to be added
933+
return x.videoId === video.videoId && x.playlistItemId === video.playlistItemId
934+
})
935+
} else {
936+
// Older playlist exports have no `playlistItemId` but have `timeAdded`
937+
// Which might be duplicate for copied playlists with duplicate `videoId`
938+
videoExists = existingPlaylist.videos.some((x) => {
939+
// Allow duplicate (by videoId) videos to be added
940+
return x.videoId === video.videoId && x.timeAdded === video.timeAdded
941+
})
942+
}
939943
} else {
940-
this.addPlaylist(playlistObject)
944+
videoExists = existingPlaylist.videos.some((x) => {
945+
// Disallow duplicate (by videoId) videos to be added
946+
return x.videoId === video.videoId
947+
})
941948
}
942-
} else {
943-
const message = this.$t('Settings.Data Settings.Playlist insufficient data', { playlist: playlistData.playlistName })
944-
showToast(message)
945-
}
949+
950+
if (!videoExists) {
951+
// Keep original `timeAdded` value
952+
const payload = {
953+
_id: existingPlaylist._id,
954+
videoData: video,
955+
}
956+
957+
this.addVideo(payload)
958+
}
959+
})
960+
// Update playlist's `lastUpdatedAt`
961+
this.updatePlaylist({ _id: existingPlaylist._id })
946962
})
947963

948964
showToast(this.$t('Settings.Data Settings.All playlists has been successfully imported'))

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ export default defineComponent({
101101
published: undefined,
102102
isLive: false,
103103
is4k: false,
104+
is8k: false,
105+
isNew: false,
106+
isVr180: false,
107+
isVr360: false,
108+
is3D: false,
104109
hasCaptions: false,
105110
isUpcoming: false,
106111
isPremium: false,
@@ -662,6 +667,11 @@ export default defineComponent({
662667
this.isLive = this.data.liveNow || this.data.lengthSeconds === 'undefined'
663668
this.isUpcoming = this.data.isUpcoming || this.data.premiere
664669
this.is4k = this.data.is4k
670+
this.is8k = this.data.is8k
671+
this.isNew = this.data.isNew
672+
this.isVr180 = this.data.isVr180
673+
this.isVr360 = this.data.isVr360
674+
this.is3D = this.data.is3d
665675
this.hasCaptions = this.data.hasCaptions
666676
this.isPremium = this.data.premium || false
667677
this.viewCount = this.data.viewCount

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

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,17 @@
146146
> • {{ $tc('Global.Counts.Watching Count', viewCount, {count: parsedViewCount}) }}</span>
147147
</div>
148148
<div
149-
v-if="is4k || hasCaptions"
149+
v-if="is4k || hasCaptions || is8k || isNew || isVr180 || isVr360 || is3D"
150150
class="videoTagLine"
151151
>
152+
<div
153+
v-if="isNew"
154+
class="videoTag"
155+
:aria-label="$t('Search Listing.Label.New')"
156+
role="img"
157+
>
158+
{{ $t('Search Listing.Label.New') }}
159+
</div>
152160
<div
153161
v-if="is4k"
154162
class="videoTag"
@@ -157,6 +165,38 @@
157165
>
158166
{{ $t('Search Listing.Label.4K') }}
159167
</div>
168+
<div
169+
v-if="is8k"
170+
class="videoTag"
171+
:aria-label="$t('Search Listing.Label.8K')"
172+
role="img"
173+
>
174+
{{ $t('Search Listing.Label.8K') }}
175+
</div>
176+
<div
177+
v-if="isVr180"
178+
class="videoTag"
179+
:aria-label="$t('Search Listing.Label.VR180')"
180+
role="img"
181+
>
182+
{{ $t('Search Listing.Label.VR180') }}
183+
</div>
184+
<div
185+
v-if="isVr360"
186+
class="videoTag"
187+
:aria-label="$t('Search Listing.Label.360 Video')"
188+
role="img"
189+
>
190+
{{ $t('Search Listing.Label.360 Video') }}
191+
</div>
192+
<div
193+
v-if="is3D"
194+
class="videoTag"
195+
:aria-label="$t('Search Listing.Label.3D')"
196+
role="img"
197+
>
198+
{{ $t('Search Listing.Label.3D') }}
199+
</div>
160200
<div
161201
v-if="hasCaptions"
162202
class="videoTag"

src/renderer/helpers/playlists.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ export const SORT_BY_VALUES = {
55
AuthorDescending: 'author_descending',
66
VideoTitleAscending: 'video_title_ascending',
77
VideoTitleDescending: 'video_title_descending',
8+
VideoDurationAscending: 'video_duration_ascending',
9+
VideoDurationDescending: 'video_duration_descending',
810
Custom: 'custom'
911
}
1012

@@ -19,7 +21,9 @@ export function getSortedPlaylistItems(playlistItems, sortOrder, locale, reverse
1921
sortOrder === SORT_BY_VALUES.VideoTitleAscending ||
2022
sortOrder === SORT_BY_VALUES.VideoTitleDescending ||
2123
sortOrder === SORT_BY_VALUES.AuthorAscending ||
22-
sortOrder === SORT_BY_VALUES.AuthorDescending
24+
sortOrder === SORT_BY_VALUES.AuthorDescending ||
25+
sortOrder === SORT_BY_VALUES.VideoDurationAscending ||
26+
sortOrder === SORT_BY_VALUES.VideoDurationDescending
2327
) {
2428
collator = new Intl.Collator([locale, 'en'])
2529
}
@@ -31,6 +35,19 @@ export function getSortedPlaylistItems(playlistItems, sortOrder, locale, reverse
3135
})
3236
}
3337

38+
export function videoDurationPresent(video) {
39+
if (typeof video.lengthSeconds !== 'number') { return false }
40+
41+
return !(isNaN(video.lengthSeconds) || video.lengthSeconds === 0)
42+
}
43+
44+
export function videoDurationWithFallback(video) {
45+
if (videoDurationPresent(video)) { return video.lengthSeconds }
46+
47+
// Fallback
48+
return 0
49+
}
50+
3451
function compareTwoPlaylistItems(a, b, sortOrder, collator) {
3552
switch (sortOrder) {
3653
case SORT_BY_VALUES.DateAddedNewest:
@@ -45,6 +62,12 @@ function compareTwoPlaylistItems(a, b, sortOrder, collator) {
4562
return collator.compare(a.author, b.author)
4663
case SORT_BY_VALUES.AuthorDescending:
4764
return collator.compare(b.author, a.author)
65+
case SORT_BY_VALUES.VideoDurationAscending: {
66+
return videoDurationWithFallback(a) - videoDurationWithFallback(b)
67+
}
68+
case SORT_BY_VALUES.VideoDurationDescending: {
69+
return videoDurationWithFallback(b) - videoDurationWithFallback(a)
70+
}
4871
default:
4972
console.error(`Unknown sortOrder: ${sortOrder}`)
5073
return 0

0 commit comments

Comments
 (0)