diff --git a/PeerTube b/PeerTube
index 6b43594..cffa06f 160000
--- a/PeerTube
+++ b/PeerTube
@@ -1 +1 @@
-Subproject commit 6b4359476c462ea178c99b0a04349f553ddb8d9d
+Subproject commit cffa06fd28bbfb03980bc7d151cfd93130c3daaf
diff --git a/client/src/components/PlaylistResult.vue b/client/src/components/PlaylistResult.vue
new file mode 100644
index 0000000..80fa6da
--- /dev/null
+++ b/client/src/components/PlaylistResult.vue
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
diff --git a/client/src/components/VideoResult.vue b/client/src/components/VideoResult.vue
index 8adb806..b40518b 100644
--- a/client/src/components/VideoResult.vue
+++ b/client/src/components/VideoResult.vue
@@ -163,7 +163,7 @@
return this.video.previewUrl
},
renderMarkdown(markdown: string) {
- return renderMarkdown(markdown)
+ return renderMarkdown(markdown)
}
}
})
diff --git a/client/src/scss/_variables.scss b/client/src/scss/_variables.scss
index 9214144..c5f7217 100644
--- a/client/src/scss/_variables.scss
+++ b/client/src/scss/_variables.scss
@@ -12,8 +12,8 @@ $small-screen: 700px;
$primary: $orange;
$secondary: $grey;
-$thumbnail-width: 223px;
-$thumbnail-height: 122px;
+$thumbnail-width: 280px;
+$thumbnail-height: 153px;
$small-font-size: 12px;
diff --git a/client/src/shared/markdown-render.ts b/client/src/shared/markdown-render.ts
index 8b9b64c..61b3e8d 100644
--- a/client/src/shared/markdown-render.ts
+++ b/client/src/shared/markdown-render.ts
@@ -16,6 +16,7 @@ for (const rule of TEXT_RULES) {
}
export function renderMarkdown(markdown: string) {
- let html = markdownIt.render(markdown);
- return html;
+ if (!markdown) return ''
+
+ return markdownIt.render(markdown)
}
diff --git a/client/src/shared/search.ts b/client/src/shared/search.ts
index a6841d1..78866ad 100644
--- a/client/src/shared/search.ts
+++ b/client/src/shared/search.ts
@@ -1,13 +1,16 @@
import axios from 'axios'
+import { VideoPlaylistsSearchQuery } from '../../../PeerTube/shared/models'
import { ResultList } from '../../../PeerTube/shared/models/result-list.model'
import { VideoChannelsSearchQuery } from '../../../PeerTube/shared/models/search/video-channels-search-query.model'
import { VideosSearchQuery } from '../../../PeerTube/shared/models/search/videos-search-query.model'
import { EnhancedVideoChannel } from '../../../server/types/channel.model'
import { EnhancedVideo } from '../../../server/types/video.model'
+import { EnhancedPlaylist } from '../../../server/types/playlist.model'
import { buildApiUrl } from './utils'
const baseVideosPath = '/api/v1/search/videos'
const baseVideoChannelsPath = '/api/v1/search/video-channels'
+const baseVideoPlaylistsPath = '/api/v1/search/video-playlists'
function searchVideos (params: VideosSearchQuery) {
const options = {
@@ -31,7 +34,19 @@ function searchVideoChannels (params: VideoChannelsSearchQuery) {
.then(res => res.data)
}
+function searchVideoPlaylists (params: VideoPlaylistsSearchQuery) {
+ const options = {
+ params
+ }
+
+ if (params.search) Object.assign(options.params, { search: params.search })
+
+ return axios.get>(buildApiUrl(baseVideoPlaylistsPath), options)
+ .then(res => res.data)
+}
+
export {
searchVideos,
- searchVideoChannels
+ searchVideoChannels,
+ searchVideoPlaylists
}
diff --git a/client/src/views/Search.vue b/client/src/views/Search.vue
index 6fd3d2d..2e69b52 100644
--- a/client/src/views/Search.vue
+++ b/client/src/views/Search.vue
@@ -20,7 +20,7 @@
- Search for your favorite videos and channels on %{instancesCount} PeerTube websites indexed by %{indexName}!
+ Search for your favorite videos, channels and playlists on %{instancesCount} PeerTube websites indexed by %{indexName}!
@@ -213,7 +213,9 @@
@@ -442,7 +444,8 @@
import SearchWarning from '../components/SearchWarning.vue'
import VideoResult from '../components/VideoResult.vue'
import ChannelResult from '../components/ChannelResult.vue'
- import { searchVideos, searchVideoChannels } from '../shared/search'
+ import PlaylistResult from '../components/PlaylistResult.vue'
+ import { searchVideos, searchVideoChannels, searchVideoPlaylists } from '../shared/search'
import { getConfig } from '../shared/config'
import { pageToAPIParams, durationRangeToAPIParams, publishedDateRangeToAPIParams, extractTagsFromQuery, buildApiUrl } from '../shared/utils'
import { SearchUrl } from '../models'
@@ -450,9 +453,10 @@
import { EnhancedVideoChannel } from '../../../server/types/channel.model'
import VueTagsInput from '@johmun/vue-tags-input'
import Pagination from '../components/Pagination.vue'
- import { VideoChannelsSearchQuery, ResultList } from '../../../PeerTube/shared/models'
+ import { VideoChannelsSearchQuery, ResultList, VideosSearchQuery } from '../../../PeerTube/shared/models'
import Nprogress from 'nprogress'
- import { VideosSearchQuery } from '../../../server/types/video-search.model'
+ import { EnhancedPlaylist } from '../../../server/types/playlist.model'
+ import { PlaylistsSearchQuery } from '../../../server/types/search-query/playlist-search.model'
export default Vue.extend({
components: {
@@ -460,6 +464,7 @@
'search-warning': SearchWarning,
'video-result': VideoResult,
'channel-result': ChannelResult,
+ 'playlist-result': PlaylistResult,
'vue-tags-input': VueTagsInput,
'pagination': Pagination
},
@@ -471,11 +476,12 @@
indexName: '',
instancesCount: 0,
indexedInstancesUrl: '',
- results: [] as (EnhancedVideo | EnhancedVideoChannel)[],
+ results: [] as (EnhancedVideo | EnhancedVideoChannel | EnhancedPlaylist)[],
resultsCount: null as number,
channelsCount: null as number,
videosCount: null as number,
+ playlistsCount: null as number,
searchedTerm: '',
@@ -483,6 +489,7 @@
pages: [],
resultsPerVideosPage: 10,
resultsPerChannelsPage: 3,
+ resultsPerPlaylistsPage: 3,
displayFilters: false,
oldQuery: '',
@@ -553,7 +560,7 @@
},
inputPlaceholder () {
- return this.$gettext('Keyword, channel, video, etc.')
+ return this.$gettext('Keyword, channel, video, playlist, etc.')
},
tagsPlaceholder () {
@@ -691,6 +698,7 @@
this.currentPage = 1
this.channelsCount = null
this.videosCount = null
+ this.playlistsCount = null
this.resultsCount = null
this.updateUrl()
@@ -705,9 +713,10 @@
Nprogress.start()
try {
- const [ videosResult, channelsResult ] = await Promise.all([
+ const [ videosResult, channelsResult, playlistsResult ] = await Promise.all([
this.searchVideos(),
- this.searchChannels()
+ this.searchChannels(),
+ this.searchPlaylists()
])
Nprogress.done()
@@ -716,9 +725,11 @@
this.channelsCount = channelsResult.total
this.videosCount = videosResult.total
+ this.playlistsCount = playlistsResult.total
- this.resultsCount = videosResult.total + channelsResult.total
- this.results = channelsResult.data.concat(videosResult.data)
+ this.resultsCount = videosResult.total + channelsResult.total + playlistsResult.total
+ this.results = channelsResult.data.concat(playlistsResult.data)
+ .concat(videosResult.data)
if (this.formSort === '-match') {
this.results.sort((r1, r2) => {
@@ -739,16 +750,30 @@
}
},
- isVideo (result: EnhancedVideo | EnhancedVideoChannel) {
+ isVideo (result: EnhancedVideo | EnhancedVideoChannel | EnhancedPlaylist) {
if ((result as EnhancedVideo).language) return true
return false
},
- getResultKey (result: EnhancedVideo | EnhancedVideoChannel) {
- if (this.isVideo(result)) return (result as EnhancedVideo).uuid
+ isPlaylist (result: EnhancedVideo | EnhancedVideoChannel | EnhancedPlaylist) {
+ if ((result as EnhancedPlaylist).videosLength !== undefined) return true
- return result.id + (result as EnhancedVideoChannel).host
+ return false
+ },
+
+ isChannel (result: EnhancedVideo | EnhancedVideoChannel | EnhancedPlaylist) {
+ if ((result as EnhancedVideoChannel).followingCount !== undefined) return true
+
+ return false
+ },
+
+ getResultKey (result: EnhancedVideo | EnhancedVideoChannel | EnhancedPlaylist) {
+ if (this.isVideo(result)) return (result as EnhancedVideo).uuid
+ if (this.isChannel(result)) return result.id + (result as EnhancedVideoChannel).host
+ if (this.isPlaylist(result)) return (result as EnhancedPlaylist).uuid
+
+ throw new Error('Unknown result')
},
updateUrl () {
@@ -864,6 +889,22 @@
} as VideoChannelsSearchQuery
},
+ buildPlaylistSearchQuery () {
+ const { start, count } = pageToAPIParams(this.currentPage, this.resultsPerChannelsPage)
+
+ let sort: string
+ if (this.formSort === '-matched') sort = '-matched'
+ else if (this.formSort === '-publishedAt') sort = '-createdAt'
+ else if (this.formSort === 'publishedAt') sort = 'createdAt'
+
+ return {
+ search: this.formSearch,
+ start,
+ sort,
+ count
+ } as PlaylistsSearchQuery
+ },
+
searchVideos (): Promise> {
if (!this.formSearch) {
return Promise.resolve({ data: [], total: 0 })
@@ -879,7 +920,7 @@
},
searchChannels (): Promise> {
- if (!this.formSearch || this.isChannelSearchDisabled()) {
+ if (!this.formSearch || this.isChannelOrPlaylistSearchDisabled()) {
return Promise.resolve({ data: [], total: 0 })
}
@@ -892,6 +933,20 @@
return searchVideoChannels(query)
},
+ searchPlaylists (): Promise> {
+ if (!this.formSearch || this.isChannelOrPlaylistSearchDisabled()) {
+ return Promise.resolve({ data: [], total: 0 })
+ }
+
+ if (!this.hasStillPlaylistsResult()) {
+ return Promise.resolve({ data: [], total: this.channelsCount })
+ }
+
+ const query = this.buildChannelSearchQuery()
+
+ return searchVideoPlaylists(query)
+ },
+
hasStillChannelsResult () {
// Not searched yet
if (this.channelsCount === null) return true
@@ -899,6 +954,13 @@
return this.getChannelsMaxPage() >= this.currentPage
},
+ hasStillPlaylistsResult () {
+ // Not searched yet
+ if (this.playlistsCount === null) return true
+
+ return this.getPlaylistsMaxPage() >= this.currentPage
+ },
+
hasStillMoreVideosResults () {
// Not searched yet
if (this.videosCount === null) return true
@@ -910,13 +972,17 @@
return Math.ceil(this.channelsCount / this.resultsPerChannelsPage)
},
+ getPlaylistsMaxPage () {
+ return Math.ceil(this.playlistsCount / this.resultsPerPlaylistsPage)
+ },
+
getVideosMaxPage () {
return Math.ceil(this.videosCount / this.resultsPerVideosPage)
},
getMaxPage () {
// Limit to 10 pages
- return Math.min(10, Math.max(this.getChannelsMaxPage(), this.getVideosMaxPage()))
+ return Math.min(10, Math.max(this.getPlaylistsMaxPage(), this.getChannelsMaxPage(), this.getVideosMaxPage()))
},
buildPages () {
@@ -964,7 +1030,7 @@
return count
},
- isChannelSearchDisabled () {
+ isChannelOrPlaylistSearchDisabled () {
return this.countActiveFilters() > 0
},
diff --git a/config/default.yaml b/config/default.yaml
index 19cd2a3..74b07c1 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -93,7 +93,7 @@ playlists-search:
# See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html for more information
# If boost == 0, the field will not be part of the search
search-fields:
- name:
+ display-name:
boost: 5
description:
boost: 1
diff --git a/config/test.yaml b/config/test.yaml
index 7a82c14..2a50d69 100644
--- a/config/test.yaml
+++ b/config/test.yaml
@@ -2,6 +2,7 @@ elastic-search:
indexes:
videos: 'peertube-index-videos-test1'
channels: 'peertube-index-channels-test1'
+ playlists: 'peertube-index-playlists-test1'
search-instance:
name_image: '/theme/framasoft/img/title.svg'
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index d2ea98e..cb8c51a 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -89,9 +89,9 @@ const CONFIG = {
},
PLAYLISTS_SEARCH: {
SEARCH_FIELDS: {
- NAME: {
- FIELD_NAME: 'name',
- BOOST: config.get('playlists-search.search-fields.name.boost')
+ DISPLAY_NAME: {
+ FIELD_NAME: 'displayName',
+ BOOST: config.get('playlists-search.search-fields.display-name.boost')
},
DESCRIPTION: {
FIELD_NAME: 'description',
@@ -124,14 +124,13 @@ const SORTABLE_COLUMNS = {
const PAGINATION_COUNT_DEFAULT = 20
const SCHEDULER_INTERVALS_MS = {
- videosIndexer: 60000 * 60 * 24 // 24 hours
+ indexation: 60000 * 60 * 24 // 24 hours
}
const INDEXER_COUNT = 10
const INDEXER_LIMIT = 500000
-const INDEXER_CONCURRENCY = 3
-
+const INDEXER_HOST_CONCURRENCY = 3
const INDEXER_QUEUE_CONCURRENCY = 3
const REQUESTS = {
@@ -167,7 +166,7 @@ function buildMultiMatchFields (fields: { [name: string]: { BOOST: number, FIELD
}
if (isTestInstance()) {
- SCHEDULER_INTERVALS_MS.videosIndexer = 1000 * 60 * 5 // 5 minutes
+ SCHEDULER_INTERVALS_MS.indexation = 1000 * 60 * 5 // 5 minutes
}
export {
@@ -179,7 +178,7 @@ export {
SORTABLE_COLUMNS,
INDEXER_QUEUE_CONCURRENCY,
SCHEDULER_INTERVALS_MS,
- INDEXER_CONCURRENCY,
+ INDEXER_HOST_CONCURRENCY,
INDEXER_COUNT,
INDEXER_LIMIT,
REQUESTS,
diff --git a/server/lib/schedulers/indexation-scheduler.ts b/server/lib/schedulers/indexation-scheduler.ts
index 69f0591..6b7eb66 100644
--- a/server/lib/schedulers/indexation-scheduler.ts
+++ b/server/lib/schedulers/indexation-scheduler.ts
@@ -2,7 +2,7 @@ import * as Bluebird from 'bluebird'
import { IndexablePlaylist } from 'server/types/playlist.model'
import { inspect } from 'util'
import { logger } from '../../helpers/logger'
-import { INDEXER_CONCURRENCY, INDEXER_COUNT, INDEXER_LIMIT, SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
+import { INDEXER_HOST_CONCURRENCY, INDEXER_COUNT, INDEXER_LIMIT, SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
import { IndexableVideo } from '../../types/video.model'
import { buildInstanceHosts } from '../elastic-search/elastic-search-instances'
import { ChannelIndexer } from '../indexers/channel-indexer'
@@ -15,7 +15,7 @@ export class IndexationScheduler extends AbstractScheduler {
private static instance: IndexationScheduler
- protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.videosIndexer
+ protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.indexation
private indexedHosts: string[] = []
@@ -68,7 +68,7 @@ export class IndexationScheduler extends AbstractScheduler {
console.error(inspect(err, { depth: 10 }))
logger.warn({ err: inspect(err) }, 'Cannot index videos from %s.', host)
}
- }, { concurrency: INDEXER_CONCURRENCY })
+ }, { concurrency: INDEXER_HOST_CONCURRENCY })
for (const o of this.indexers) {
await o.refreshIndex()
@@ -91,10 +91,10 @@ export class IndexationScheduler extends AbstractScheduler {
logger.debug('Getting video results from %s (from = %d).', host, start)
videos = await getVideos(host, start)
- start += videos.length
-
logger.debug('Got %d video results from %s (from = %d).', videos.length, host, start)
+ start += videos.length
+
if (videos.length !== 0) {
const { created } = await this.videoIndexer.indexElements(videos)
@@ -139,10 +139,10 @@ export class IndexationScheduler extends AbstractScheduler {
logger.debug('Getting playlist results from %s (from = %d, channelHandle = %s).', host, start, channelHandle)
playlists = await getPlaylistsOf(host, channelHandle, start)
- start += playlists.length
-
logger.debug('Got %d playlist results from %s (from = %d, channelHandle = %s).', playlists.length, host, start, channelHandle)
+ start += playlists.length
+
if (playlists.length !== 0) {
await this.playlistIndexer.indexElements(playlists)