2020-08-27 14:44:21 +02:00
|
|
|
<template>
|
|
|
|
<div>
|
|
|
|
<my-header v-bind:indexName="indexName"></my-header>
|
|
|
|
|
|
|
|
<main>
|
2020-09-02 10:17:50 +02:00
|
|
|
<h3 v-bind:class="{ 'none-opacity': !instancesCount }" v-translate="{ instancesCount: instancesCount, indexName: indexName, indexedInstancesUrl: indexedInstancesUrl }">
|
|
|
|
Search for your favorite videos and channels on <a href="%{indexedInstancesUrl}" target="_blank">%{instancesCount} PeerTube websites</a> listed on <strong>%{indexName}</strong>!
|
2020-08-27 14:44:21 +02:00
|
|
|
</h3>
|
|
|
|
|
|
|
|
<div id="search-anchor" class="search-container">
|
2020-09-01 11:50:55 +02:00
|
|
|
<input v-bind:placeholder="inputPlaceholder" autofocus v-on:keyup.enter="doNewSearch()" type="text" v-model="formSearch" name="search-text" />
|
2020-08-27 14:44:21 +02:00
|
|
|
|
2020-09-01 11:05:00 +02:00
|
|
|
<button v-on:click="doNewSearch()">
|
2020-09-01 11:50:55 +02:00
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
2020-08-27 14:44:21 +02:00
|
|
|
<circle cx="11" cy="11" r="8"></circle>
|
|
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
|
|
</svg>
|
|
|
|
|
2020-09-01 11:50:55 +02:00
|
|
|
<translate>Go!</translate>
|
2020-08-27 14:44:21 +02:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<search-warning class="search-warning" v-bind:indexName="indexName" v-bind:highlight="!searchDone"></search-warning>
|
|
|
|
|
|
|
|
<div class="results" v-if="searchDone">
|
|
|
|
<div class="filters" v-if="searchDone && searchedTerm">
|
|
|
|
<div class="button-rows">
|
|
|
|
<button class="filters-button peertube-button" v-on:click="toggleFilters()">
|
2020-09-01 11:50:55 +02:00
|
|
|
<translate>Filters</translate>
|
|
|
|
<span v-if="countActiveFilters()" v-translate> ({{ countActiveFilters() }} active)</span>
|
2020-08-27 14:44:21 +02:00
|
|
|
|
|
|
|
<div class="arrow-down"></div>
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<div class="sort-select">
|
2020-09-01 11:50:55 +02:00
|
|
|
<label for="sort" v-translate>Sort by:</label>
|
2020-08-27 14:44:21 +02:00
|
|
|
<div class="sort-select select-container">
|
|
|
|
<select id="sort" name="sort" v-model="formSort">
|
2020-09-01 11:50:55 +02:00
|
|
|
<option v-translate value="-match">Best match</option>
|
|
|
|
<option v-translate value="-publishedAt">Most recent</option>
|
|
|
|
<option v-translate value="publishedAt">Least recent</option>
|
2020-08-27 14:44:21 +02:00
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<form v-if="displayFilters" class="filters-content">
|
|
|
|
<div class="form-group">
|
|
|
|
<div class="radio-label label-container">
|
2020-09-01 11:50:55 +02:00
|
|
|
<label v-translate>Display sensitive content</label>
|
2020-08-27 14:44:21 +02:00
|
|
|
|
2020-09-01 11:50:55 +02:00
|
|
|
<button v-translate class="reset-button" v-on:click="resetField('nsfw')" v-if="formNSFW !== undefined">
|
2020-08-27 14:44:21 +02:00
|
|
|
Reset
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="radio-container">
|
|
|
|
<input type="radio" name="sensitiveContent" id="sensitiveContentYes" value="both" v-model="formNSFW">
|
2020-09-01 11:50:55 +02:00
|
|
|
<label v-translate for="sensitiveContentYes" class="radio">Yes</label>
|
2020-08-27 14:44:21 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="radio-container">
|
|
|
|
<input type="radio" name="sensitiveContent" id="sensitiveContentNo" value="false" v-model="formNSFW">
|
2020-09-01 11:50:55 +02:00
|
|
|
<label v-translate for="sensitiveContentNo" class="radio">No</label>
|
2020-08-27 14:44:21 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
<div class="radio-label label-container">
|
2020-09-01 11:50:55 +02:00
|
|
|
<label v-translate>Published date</label>
|
2020-08-27 14:44:21 +02:00
|
|
|
|
2020-09-01 11:50:55 +02:00
|
|
|
<button v-translate class="reset-button" v-on:click="resetField('publishedDateRange')" v-if="formPublishedDateRange !== undefined">
|
2020-08-27 14:44:21 +02:00
|
|
|
Reset
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="radio-container" v-for="date in publishedDateRanges" :key="date.id">
|
|
|
|
<input type="radio" name="publishedDateRange" v-bind:id="date.id" v-bind:value="date.id" v-model="formPublishedDateRange">
|
|
|
|
<label v-bind:for="date.id" class="radio">{{ date.label }}</label>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
<div class="radio-label label-container">
|
2020-09-01 11:50:55 +02:00
|
|
|
<label v-translate>Duration</label>
|
|
|
|
<button v-translate class="reset-button" v-on:click="resetField('durationRange')" v-if="formDurationRange !== undefined">
|
2020-08-27 14:44:21 +02:00
|
|
|
Reset
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="radio-container" v-for="duration in durationRanges" :key="duration.id">
|
|
|
|
<input type="radio" name="durationRange" v-bind:id="duration.id" v-bind:value="duration.id" v-model="formDurationRange">
|
|
|
|
<label v-bind:for="duration.id" class="radio">{{ duration.label }}</label>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="form-group">
|
2020-09-01 11:50:55 +02:00
|
|
|
<label v-translate for="category">Category</label>
|
|
|
|
<button v-translate class="reset-button" v-on:click="resetField('categoryOneOf')" v-if="formCategoryOneOf !== undefined">
|
2020-08-27 14:44:21 +02:00
|
|
|
Reset
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<div class="select-container">
|
|
|
|
<select id="category" name="category" v-model="formCategoryOneOf">
|
2020-09-01 11:50:55 +02:00
|
|
|
<option v-translate v-bind:value="undefined">Display all categories</option>
|
2020-08-27 14:44:21 +02:00
|
|
|
<option v-for="category in videoCategories" v-bind:value="category.id" :key="category.id">{{ category.label }}</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="form-group">
|
2020-09-01 11:50:55 +02:00
|
|
|
<label v-translate for="licence">Licence</label>
|
|
|
|
<button v-translate class="reset-button" v-on:click="resetField('licenceOneOf')" v-if="formLicenceOneOf !== undefined">
|
2020-08-27 14:44:21 +02:00
|
|
|
Reset
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<div class="select-container">
|
|
|
|
<select id="licence" name="licence" v-model="formLicenceOneOf">
|
2020-09-01 11:50:55 +02:00
|
|
|
<option v-translate v-bind:value="undefined">Display all licenses</option>
|
2020-08-27 14:44:21 +02:00
|
|
|
<option v-for="licence in videoLicences" v-bind:value="licence.id" :key="licence.id">{{ licence.label }}</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="form-group">
|
2020-09-01 11:50:55 +02:00
|
|
|
<label v-translate for="language">Language</label>
|
|
|
|
<button v-translate class="reset-button" v-on:click="resetField('languageOneOf')" v-if="formLanguageOneOf !== undefined">
|
2020-08-27 14:44:21 +02:00
|
|
|
Reset
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<div class="select-container">
|
|
|
|
<select id="language" name="language" v-model="formLanguageOneOf">
|
2020-09-01 11:50:55 +02:00
|
|
|
<option v-translate v-bind:value="undefined">Display all languages</option>
|
2020-08-27 14:44:21 +02:00
|
|
|
<option v-for="language in videoLanguages" v-bind:value="language.id" :key="language.id">{{ language.label }}</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="form-group">
|
2020-09-01 11:50:55 +02:00
|
|
|
<label v-translate for="tagsAllOf">All of these tags</label>
|
|
|
|
<button v-translate class="reset-button" v-on:click="resetField('tagsAllOf')" v-if="formTagsAllOf.length !== 0">
|
2020-08-27 14:44:21 +02:00
|
|
|
Reset
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<vue-tags-input @tags-changed="newTags => formTagsAllOf = newTags" v-model="formTagAllOf" :tags="formTagsAllOf" />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="form-group">
|
2020-09-01 11:50:55 +02:00
|
|
|
<label v-translate for="tagsOneOf">One of these tags</label>
|
|
|
|
<button v-translate class="reset-button" v-on:click="resetField('tagsOneOf')" v-if="formTagsOneOf.length !== 0">
|
2020-08-27 14:44:21 +02:00
|
|
|
Reset
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<vue-tags-input @tags-changed="newTags => formTagsOneOf = newTags" v-model="formTagOneOf" :tags="formTagsOneOf" />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="button-block">
|
2020-09-02 11:09:20 +02:00
|
|
|
<input class="peertube-button" type="button" v-bind:value="applyFiltersLabel" v-on:click="doNewSearch()" />
|
2020-08-27 14:44:21 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
|
2020-09-02 11:16:56 +02:00
|
|
|
<div id="results-anchor" class="results-summary" v-if="(searchedTerm && resultsCount === 0) || (resultsCount !== null && resultsCount !== 0)">
|
2020-09-02 11:24:02 +02:00
|
|
|
<span v-if="searchedTerm && resultsCount === 0" v-translate>No results found for</span>
|
|
|
|
<span v-if="resultsCount !== null && resultsCount !== 0" v-translate="{ resultsCount: resultsCount }">%{resultsCount} results found for</span>
|
|
|
|
<strong>{{ searchedTerm }} <span v-if="countActiveFilters() > 0" v-translate>with {{ countActiveFilters() }} active filters</span> </strong>
|
2020-09-01 15:52:16 +02:00
|
|
|
<span v-translate="{ instancesCount: instancesCount }">on %{instancesCount} indexed PeerTube websites</span>
|
2020-08-27 14:44:21 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div v-for="result in results" :key="getResultKey(result)">
|
|
|
|
<video-result v-if="isVideo(result)" v-bind:video="result"></video-result>
|
|
|
|
|
|
|
|
<channel-result v-else v-bind:channel="result"></channel-result>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<pagination v-bind:maxPage="getMaxPage()" v-bind:searchDone="searchDone" v-bind:currentPage="currentPage" v-bind:pages="pages"></pagination>
|
|
|
|
</div>
|
|
|
|
</main>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
@import '../scss/_variables';
|
|
|
|
|
|
|
|
main {
|
|
|
|
margin: auto;
|
|
|
|
}
|
|
|
|
|
|
|
|
h3 {
|
|
|
|
max-width: 600px;
|
|
|
|
text-align: center;
|
|
|
|
margin: auto;
|
|
|
|
font-weight: normal;
|
|
|
|
font-size: 16px;
|
|
|
|
|
2020-09-02 10:17:50 +02:00
|
|
|
a {
|
|
|
|
color: $orange-darken;
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
color: $orange;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-27 14:44:21 +02:00
|
|
|
@media screen and (max-width: $small-screen) {
|
|
|
|
font-size: 14px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.search-container {
|
|
|
|
background-color: #fff;
|
|
|
|
border-radius: 2px;
|
|
|
|
position: relative;
|
|
|
|
max-width: 500px;
|
|
|
|
height: 45px;
|
|
|
|
margin: auto;
|
|
|
|
margin-top: 30px;
|
|
|
|
|
|
|
|
input[type=text] {
|
|
|
|
background-color: transparent;
|
|
|
|
outline: none;
|
|
|
|
height: 35px;
|
|
|
|
font-size: 15px;
|
|
|
|
border: 0;
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
|
|
padding: 0 20px;
|
|
|
|
}
|
|
|
|
|
|
|
|
button {
|
|
|
|
border-radius: 2px;
|
|
|
|
cursor: pointer;
|
|
|
|
position: absolute;
|
|
|
|
right: 0;
|
|
|
|
background-color: $orange-darken;
|
|
|
|
border: 0;
|
|
|
|
color: #FFF;
|
|
|
|
height: 100%;
|
|
|
|
outline: 0;
|
|
|
|
font-size: 15px;
|
|
|
|
padding: 0 15px 0 10px;
|
|
|
|
display: inline-flex;
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
svg {
|
|
|
|
margin-right: 10px;
|
|
|
|
}
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
background-color: $orange;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.search-warning {
|
|
|
|
margin: 50px 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.results-summary,
|
|
|
|
.no-results {
|
|
|
|
margin-top: 50px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.results {
|
|
|
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
|
|
|
padding-top: 20px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.filters-content {
|
|
|
|
display: flex;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
|
|
.form-group:nth-child(2n-1) {
|
|
|
|
padding-right: 20px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.form-group {
|
|
|
|
width: 50%;
|
|
|
|
min-height: 80px;
|
|
|
|
display: inline-block;
|
|
|
|
margin: 10px 0;
|
|
|
|
font-size: 14px;
|
|
|
|
}
|
|
|
|
|
|
|
|
@media screen and (max-width: $small-view) {
|
|
|
|
.form-group {
|
|
|
|
width: 100%;
|
|
|
|
padding-right: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.form-group > label,
|
|
|
|
.label-container label {
|
|
|
|
font-weight: $font-semibold;
|
|
|
|
}
|
|
|
|
|
|
|
|
.form-group > label,
|
|
|
|
.label-container {
|
|
|
|
display: inline-block;
|
|
|
|
margin-bottom: 5px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.radio-container {
|
|
|
|
display: inline-block;
|
|
|
|
margin: 0 10px 5px 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.button-block {
|
|
|
|
width: 100%;
|
|
|
|
text-align: right;
|
|
|
|
}
|
|
|
|
|
|
|
|
.radio-label {
|
|
|
|
display: block;
|
|
|
|
}
|
|
|
|
|
|
|
|
.reset-button {
|
|
|
|
background: none;
|
|
|
|
border: none;
|
|
|
|
font-weight: 600;
|
|
|
|
font-size: 11px;
|
|
|
|
opacity: 0.7;
|
|
|
|
margin-left: 5px;
|
|
|
|
cursor: pointer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.filters-button {
|
|
|
|
position: relative;
|
|
|
|
padding-right: 25px;
|
|
|
|
|
|
|
|
.arrow-down {
|
|
|
|
$size: 4px;
|
|
|
|
|
|
|
|
position: absolute;
|
|
|
|
right: 0;
|
|
|
|
top: 50%;
|
|
|
|
margin-top: -$size/2;
|
|
|
|
margin-right: 10px;
|
|
|
|
width: 0;
|
|
|
|
height: 0;
|
|
|
|
border-left: $size solid transparent;
|
|
|
|
border-right: $size solid transparent;
|
|
|
|
|
|
|
|
border-top: $size solid #fff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.button-rows {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
|
|
.sort-select {
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
label {
|
|
|
|
color: $grey;
|
|
|
|
font-size: 14px;
|
|
|
|
min-width: fit-content;
|
|
|
|
margin: auto 5px auto 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.select-container {
|
|
|
|
max-width: 150px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
import Vue from 'vue'
|
|
|
|
import Header from '../components/Header.vue'
|
|
|
|
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 { getConfig } from '../shared/config'
|
|
|
|
import { pageToAPIParams, durationRangeToAPIParams, publishedDateRangeToAPIParams, extractTagsFromQuery } from '../shared/utils'
|
|
|
|
import { SearchUrl } from '../models'
|
|
|
|
import { EnhancedVideo } from '../../../server/types/video.model'
|
|
|
|
import { EnhancedVideoChannel } from '../../../server/types/channel.model'
|
|
|
|
import VueTagsInput from '@johmun/vue-tags-input'
|
|
|
|
import Pagination from '../components/Pagination.vue'
|
|
|
|
import { VideosSearchQuery, VideoChannelsSearchQuery, ResultList } from '../../../PeerTube/shared/models'
|
|
|
|
|
|
|
|
export default Vue.extend({
|
|
|
|
components: {
|
|
|
|
'my-header': Header,
|
|
|
|
'search-warning': SearchWarning,
|
|
|
|
'video-result': VideoResult,
|
|
|
|
'channel-result': ChannelResult,
|
|
|
|
'vue-tags-input': VueTagsInput,
|
|
|
|
'pagination': Pagination
|
|
|
|
},
|
|
|
|
|
|
|
|
data () {
|
|
|
|
return {
|
|
|
|
searchDone: false,
|
|
|
|
|
|
|
|
indexName: '',
|
2020-09-01 15:52:16 +02:00
|
|
|
instancesCount: 0,
|
2020-09-02 10:17:50 +02:00
|
|
|
indexedInstancesUrl: '',
|
2020-08-27 14:44:21 +02:00
|
|
|
results: [] as (EnhancedVideo | EnhancedVideoChannel)[],
|
|
|
|
|
|
|
|
resultsCount: null as number,
|
|
|
|
channelsCount: null as number,
|
|
|
|
videosCount: null as number,
|
|
|
|
|
|
|
|
searchedTerm: '',
|
|
|
|
|
|
|
|
currentPage: 1,
|
|
|
|
pages: [],
|
|
|
|
resultsPerVideosPage: 10,
|
2020-09-02 11:37:02 +02:00
|
|
|
resultsPerChannelsPage: 3,
|
2020-08-27 14:44:21 +02:00
|
|
|
|
|
|
|
displayFilters: false,
|
|
|
|
oldQuery: '',
|
|
|
|
|
|
|
|
formSearch: '',
|
|
|
|
formSort: '-match',
|
|
|
|
|
|
|
|
formNSFW: undefined,
|
|
|
|
formPublishedDateRange: undefined,
|
|
|
|
formDurationRange: undefined,
|
|
|
|
formCategoryOneOf: undefined,
|
|
|
|
formLicenceOneOf: undefined,
|
|
|
|
formLanguageOneOf: undefined,
|
|
|
|
|
|
|
|
formTagAllOf: '',
|
|
|
|
formTagOneOf: '',
|
|
|
|
formTagsAllOf: [],
|
|
|
|
formTagsOneOf: []
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
async mounted () {
|
|
|
|
const config = await getConfig()
|
|
|
|
|
|
|
|
this.instancesCount = config.indexedHostsCount
|
2020-09-02 10:17:50 +02:00
|
|
|
this.indexedInstancesUrl = config.indexedInstancesUrl
|
2020-08-27 14:44:21 +02:00
|
|
|
this.indexName = config.searchInstanceName
|
|
|
|
|
2020-09-02 14:12:41 +02:00
|
|
|
if (this.loadUrl()) {
|
|
|
|
this.doSearch()
|
|
|
|
}
|
2020-08-27 14:44:21 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
watch: {
|
2020-09-02 11:09:20 +02:00
|
|
|
// For pagination change
|
2020-08-27 14:44:21 +02:00
|
|
|
$route(to, from) {
|
|
|
|
if (!this.searchDone) return
|
|
|
|
|
2020-09-02 11:09:20 +02:00
|
|
|
const urlPage = this.$route.query.page
|
|
|
|
|
|
|
|
if (urlPage && parseInt(urlPage) !== this.currentPage) {
|
|
|
|
this.loadUrl()
|
|
|
|
this.doSearch(false)
|
|
|
|
this.scrollToResults()
|
|
|
|
}
|
2020-08-27 14:44:21 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
formSort () {
|
|
|
|
this.doSearch()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
computed: {
|
2020-09-02 10:17:50 +02:00
|
|
|
applyFiltersLabel () {
|
|
|
|
return this.$gettext('Apply filters')
|
|
|
|
},
|
|
|
|
|
2020-09-01 11:50:55 +02:00
|
|
|
inputPlaceholder () {
|
|
|
|
return this.$gettext('Keyword, channel, video, etc.')
|
|
|
|
},
|
|
|
|
|
2020-08-27 14:44:21 +02:00
|
|
|
publishedDateRanges () {
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
id: 'any_published_date',
|
2020-09-01 11:50:55 +02:00
|
|
|
label: this.$gettext('Any')
|
2020-08-27 14:44:21 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'today',
|
2020-09-01 11:50:55 +02:00
|
|
|
label: this.$gettext('Today')
|
2020-08-27 14:44:21 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'last_7days',
|
2020-09-01 11:50:55 +02:00
|
|
|
label: this.$gettext('Last 7 days')
|
2020-08-27 14:44:21 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'last_30days',
|
2020-09-01 11:50:55 +02:00
|
|
|
label: this.$gettext('Last 30 days')
|
2020-08-27 14:44:21 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'last_365days',
|
2020-09-01 11:50:55 +02:00
|
|
|
label: this.$gettext('Last 365 days')
|
2020-08-27 14:44:21 +02:00
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
|
|
|
|
durationRanges () {
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
id: 'any_duration',
|
2020-09-01 11:50:55 +02:00
|
|
|
label: this.$gettext('Any')
|
2020-08-27 14:44:21 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'short',
|
2020-09-01 11:50:55 +02:00
|
|
|
label: this.$gettext('Short (< 4 min)')
|
2020-08-27 14:44:21 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'medium',
|
2020-09-01 11:50:55 +02:00
|
|
|
label: this.$gettext('Medium (4-10 min)')
|
2020-08-27 14:44:21 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'long',
|
2020-09-01 11:50:55 +02:00
|
|
|
label: this.$gettext('Long (> 10 min)')
|
2020-08-27 14:44:21 +02:00
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
|
|
|
|
videoCategories () {
|
|
|
|
return [
|
|
|
|
{ id: '1', label: this.$gettext('Music') },
|
|
|
|
{ id: '2', label: this.$gettext('Films') },
|
|
|
|
{ id: '3', label: this.$gettext('Vehicles') },
|
|
|
|
{ id: '4', label: this.$gettext('Art') },
|
|
|
|
{ id: '5', label: this.$gettext('Sports') },
|
|
|
|
{ id: '6', label: this.$gettext('Travels') },
|
|
|
|
{ id: '7', label: this.$gettext('Gaming') },
|
|
|
|
{ id: '8', label: this.$gettext('People') },
|
|
|
|
{ id: '9', label: this.$gettext('Comedy') },
|
|
|
|
{ id: '10', label: this.$gettext('Entertainment') },
|
|
|
|
{ id: '11', label: this.$gettext('News & Politics') },
|
|
|
|
{ id: '12', label: this.$gettext('How To') },
|
|
|
|
{ id: '13', label: this.$gettext('Education') },
|
|
|
|
{ id: '14', label: this.$gettext('Activism') },
|
|
|
|
{ id: '15', label: this.$gettext('Science & Technology') },
|
|
|
|
{ id: '16', label: this.$gettext('Animals') },
|
|
|
|
{ id: '17', label: this.$gettext('Kids') },
|
|
|
|
{ id: '18', label: this.$gettext('Food') }
|
|
|
|
]
|
|
|
|
},
|
|
|
|
|
|
|
|
videoLicences () {
|
|
|
|
return [
|
|
|
|
{ id: '1', label: this.$gettext('Attribution') },
|
|
|
|
{ id: '2', label: this.$gettext('Attribution - Share Alike') },
|
|
|
|
{ id: '3', label: this.$gettext('Attribution - No Derivatives') },
|
|
|
|
{ id: '4', label: this.$gettext('Attribution - Non Commercial') },
|
|
|
|
{ id: '5', label: this.$gettext('Attribution - Non Commercial - Share Alike') },
|
|
|
|
{ id: '6', label: this.$gettext('Attribution - Non Commercial - No Derivatives') },
|
|
|
|
{ id: '7', label: this.$gettext('Public Domain Dedication') }
|
|
|
|
]
|
|
|
|
},
|
|
|
|
|
|
|
|
videoLanguages () {
|
|
|
|
return [
|
|
|
|
{ id: 'en', label: this.$gettext('English') },
|
|
|
|
{ id: 'fr', label: this.$gettext('Français') },
|
|
|
|
{ id: 'ja', label: this.$gettext('日本語') },
|
|
|
|
{ id: 'eu', label: this.$gettext('Euskara') },
|
|
|
|
{ id: 'ca', label: this.$gettext('Català') },
|
|
|
|
{ id: 'cs', label: this.$gettext('Čeština') },
|
|
|
|
{ id: 'eo', label: this.$gettext('Esperanto') },
|
|
|
|
{ id: 'el', label: this.$gettext('ελληνικά') },
|
|
|
|
{ id: 'de', label: this.$gettext('Deutsch') },
|
|
|
|
{ id: 'it', label: this.$gettext('Italiano') },
|
|
|
|
{ id: 'nl', label: this.$gettext('Nederlands') },
|
|
|
|
{ id: 'es', label: this.$gettext('Español') },
|
|
|
|
{ id: 'oc', label: this.$gettext('Occitan') },
|
|
|
|
{ id: 'gd', label: this.$gettext('Gàidhlig') },
|
|
|
|
{ id: 'zh', label: this.$gettext('简体中文(中国)') },
|
|
|
|
{ id: 'pt', label: this.$gettext('Português (Portugal)') },
|
|
|
|
{ id: 'sv', label: this.$gettext('svenska') },
|
|
|
|
{ id: 'pl', label: this.$gettext('Polski') },
|
|
|
|
{ id: 'fi', label: this.$gettext('suomi') },
|
|
|
|
{ id: 'ru', label: this.$gettext('русский') }
|
|
|
|
]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
|
|
doNewSearch (updateUrl = true) {
|
|
|
|
this.currentPage = 1
|
|
|
|
this.channelsCount = null
|
|
|
|
this.videosCount = null
|
|
|
|
this.resultsCount = null
|
|
|
|
|
|
|
|
return this.doSearch(updateUrl)
|
|
|
|
},
|
|
|
|
|
|
|
|
async doSearch (updateUrl = true) {
|
|
|
|
if (updateUrl) this.updateUrl()
|
|
|
|
|
|
|
|
this.results = []
|
|
|
|
|
|
|
|
const [ videosResult, channelsResult ] = await Promise.all([
|
|
|
|
this.searchVideos(),
|
|
|
|
this.searchChannels()
|
|
|
|
])
|
|
|
|
|
|
|
|
this.channelsCount = channelsResult.total
|
|
|
|
this.videosCount = videosResult.total
|
|
|
|
|
|
|
|
this.resultsCount = videosResult.total + channelsResult.total
|
|
|
|
this.results = channelsResult.data.concat(videosResult.data)
|
|
|
|
|
2020-09-02 11:37:02 +02:00
|
|
|
if (this.formSort === '-match') {
|
|
|
|
this.results.sort((r1, r2) => {
|
|
|
|
if (r1.score < r2.score) return 1
|
|
|
|
else if (r1.score === r2.score) return 0
|
2020-08-27 14:44:21 +02:00
|
|
|
|
2020-09-02 11:37:02 +02:00
|
|
|
return -1
|
|
|
|
})
|
|
|
|
}
|
2020-08-27 14:44:21 +02:00
|
|
|
|
|
|
|
this.buildPages()
|
|
|
|
this.searchDone = true
|
|
|
|
this.searchedTerm = this.formSearch
|
|
|
|
},
|
|
|
|
|
2020-09-01 11:26:11 +02:00
|
|
|
isVideo (result: EnhancedVideo | EnhancedVideoChannel) {
|
2020-08-27 14:44:21 +02:00
|
|
|
if ((result as EnhancedVideo).language) return true
|
|
|
|
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
|
|
|
|
getResultKey (result: EnhancedVideo | EnhancedVideoChannel) {
|
|
|
|
if (this.isVideo(result)) return (result as EnhancedVideo).uuid
|
|
|
|
|
|
|
|
return result.id + (result as EnhancedVideoChannel).host
|
|
|
|
},
|
|
|
|
|
|
|
|
updateUrl () {
|
|
|
|
const query: SearchUrl = {
|
|
|
|
search: this.formSearch,
|
|
|
|
sort: this.formSort,
|
|
|
|
nsfw: this.formNSFW,
|
|
|
|
publishedDateRange: this.formPublishedDateRange,
|
|
|
|
durationRange: this.formDurationRange,
|
|
|
|
categoryOneOf: this.formCategoryOneOf,
|
|
|
|
licenceOneOf: this.formLicenceOneOf,
|
|
|
|
languageOneOf: this.formLanguageOneOf,
|
|
|
|
tagsAllOf: this.formTagsAllOf.map(t => t.text),
|
2020-09-02 11:09:20 +02:00
|
|
|
tagsOneOf: this.formTagsOneOf.map(t => t.text),
|
|
|
|
page: this.currentPage
|
2020-08-27 14:44:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
this.$router.push({ path: '/search', query })
|
|
|
|
},
|
|
|
|
|
|
|
|
loadUrl () {
|
|
|
|
const query = this.$route.query as SearchUrl & { page: number }
|
2020-09-02 14:12:41 +02:00
|
|
|
if (Object.keys(query).length === 0) return false
|
2020-08-27 14:44:21 +02:00
|
|
|
|
|
|
|
if (query.search) this.formSearch = query.search
|
|
|
|
if (query.nsfw) this.formNSFW = query.nsfw
|
|
|
|
if (query.publishedDateRange) this.formPublishedDateRange = query.publishedDateRange
|
|
|
|
if (query.durationRange) this.formDurationRange = query.durationRange
|
|
|
|
if (query.categoryOneOf) this.formCategoryOneOf = query.categoryOneOf
|
|
|
|
if (query.licenceOneOf) this.formLicenceOneOf = query.licenceOneOf
|
|
|
|
if (query.languageOneOf) this.formLanguageOneOf = query.languageOneOf
|
|
|
|
if (query.tagsAllOf) this.formTagsAllOf = extractTagsFromQuery(query.tagsAllOf)
|
|
|
|
if (query.tagsOneOf) this.formTagsOneOf = extractTagsFromQuery(query.tagsOneOf)
|
|
|
|
|
|
|
|
if (query.sort) this.formSort = query.sort
|
|
|
|
|
|
|
|
if (query.page && this.currentPage !== query.page) {
|
|
|
|
this.currentPage = parseInt(query.page + '')
|
|
|
|
}
|
2020-09-02 14:12:41 +02:00
|
|
|
|
|
|
|
return true
|
2020-08-27 14:44:21 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
buildVideoSearchQuery () {
|
|
|
|
const { start, count } = pageToAPIParams(this.currentPage, this.resultsPerVideosPage)
|
|
|
|
const { durationMin, durationMax } = durationRangeToAPIParams(this.formDurationRange)
|
|
|
|
const { startDate, endDate } = publishedDateRangeToAPIParams(this.formPublishedDateRange)
|
|
|
|
|
|
|
|
return {
|
|
|
|
search: this.formSearch,
|
|
|
|
|
|
|
|
durationMin,
|
|
|
|
durationMax,
|
|
|
|
|
|
|
|
startDate,
|
|
|
|
endDate,
|
|
|
|
|
|
|
|
nsfw: this.nsfw,
|
|
|
|
|
|
|
|
categoryOneOf: this.formCategoryOneOf ? [ this.formCategoryOneOf ] : undefined,
|
|
|
|
licenceOneOf: this.formLicenceOneOf ? [ this.formLicenceOneOf ] : undefined,
|
|
|
|
languageOneOf: this.formLanguageOneOf ? [ this.formLanguageOneOf ] : undefined,
|
|
|
|
|
|
|
|
tagsOneOf: this.formTagsOneOf.map(t => t.text),
|
|
|
|
tagsAllOf: this.formTagsAllOf.map(t => t.text),
|
|
|
|
|
|
|
|
start,
|
|
|
|
count,
|
|
|
|
sort: this.formSort
|
|
|
|
} as VideosSearchQuery
|
|
|
|
},
|
|
|
|
|
|
|
|
buildChannelSearchQuery () {
|
|
|
|
const { start, count } = pageToAPIParams(this.currentPage, this.resultsPerChannelsPage)
|
|
|
|
|
2020-09-02 11:37:02 +02:00
|
|
|
let sort: string
|
|
|
|
if (this.formSort === '-matched') sort = '-matched'
|
|
|
|
else if (this.formSort === '-publishedAt') sort = '-createdAt'
|
|
|
|
else if (this.formSort === 'publishedAt') sort = 'createdAt'
|
|
|
|
|
2020-08-27 14:44:21 +02:00
|
|
|
return {
|
|
|
|
search: this.formSearch,
|
|
|
|
start,
|
2020-09-02 11:37:02 +02:00
|
|
|
sort,
|
2020-08-27 14:44:21 +02:00
|
|
|
count
|
|
|
|
} as VideoChannelsSearchQuery
|
|
|
|
},
|
|
|
|
|
|
|
|
searchVideos (): Promise<ResultList<EnhancedVideo>> {
|
|
|
|
if (!this.formSearch) {
|
|
|
|
return Promise.resolve({ data: [], total: 0 })
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.hasStillMoreVideosResults()) {
|
|
|
|
return Promise.resolve({ data: [], total: this.videosCount })
|
|
|
|
}
|
|
|
|
|
|
|
|
const query = this.buildVideoSearchQuery()
|
|
|
|
|
|
|
|
return searchVideos(query)
|
|
|
|
},
|
|
|
|
|
|
|
|
searchChannels (): Promise<ResultList<EnhancedVideoChannel>> {
|
|
|
|
if (!this.formSearch || this.isChannelSearchDisabled()) {
|
|
|
|
return Promise.resolve({ data: [], total: 0 })
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.hasStillChannelsResult()) {
|
|
|
|
return Promise.resolve({ data: [], total: this.channelsCount })
|
|
|
|
}
|
|
|
|
|
|
|
|
const query = this.buildChannelSearchQuery()
|
|
|
|
|
|
|
|
return searchVideoChannels(query)
|
|
|
|
},
|
|
|
|
|
|
|
|
hasStillChannelsResult () {
|
|
|
|
// Not searched yet
|
|
|
|
if (this.channelsCount === null) return true
|
|
|
|
|
|
|
|
return this.getChannelsMaxPage() >= this.currentPage
|
|
|
|
},
|
|
|
|
|
|
|
|
hasStillMoreVideosResults () {
|
|
|
|
// Not searched yet
|
|
|
|
if (this.videosCount === null) return true
|
|
|
|
|
|
|
|
return this.getVideosMaxPage() >= this.currentPage
|
|
|
|
},
|
|
|
|
|
|
|
|
getChannelsMaxPage () {
|
|
|
|
return Math.ceil(this.channelsCount / this.resultsPerChannelsPage)
|
|
|
|
},
|
|
|
|
|
|
|
|
getVideosMaxPage () {
|
|
|
|
return Math.ceil(this.videosCount / this.resultsPerVideosPage)
|
|
|
|
},
|
|
|
|
|
|
|
|
getMaxPage () {
|
|
|
|
// Limit to 10 pages
|
|
|
|
return Math.min(10, Math.max(this.getChannelsMaxPage(), this.getVideosMaxPage()))
|
|
|
|
},
|
|
|
|
|
|
|
|
buildPages () {
|
|
|
|
this.pages = []
|
|
|
|
|
|
|
|
for (let i = 1; i <= this.getMaxPage(); i++) {
|
|
|
|
this.pages.push(i)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleFilters () {
|
|
|
|
this.displayFilters = !this.displayFilters
|
|
|
|
},
|
|
|
|
|
|
|
|
resetField (field: string) {
|
|
|
|
if (field === 'nsfw') this.formNSFW = undefined
|
|
|
|
else if (field === 'publishedDateRange') this.formPublishedDateRange = undefined
|
|
|
|
else if (field === 'durationRange') this.formDurationRange = undefined
|
|
|
|
else if (field === 'categoryOneOf') this.formCategoryOneOf = undefined
|
|
|
|
else if (field === 'licenceOneOf') this.formLicenceOneOf = undefined
|
|
|
|
else if (field === 'languageOneOf') this.formLanguageOneOf = undefined
|
|
|
|
else if (field === 'tagsAllOf') this.formTagsAllOf = []
|
|
|
|
else if (field === 'tagsOneOf') this.formTagsOneOf = []
|
|
|
|
},
|
|
|
|
|
|
|
|
countActiveFilters () {
|
|
|
|
let count = 0
|
|
|
|
|
|
|
|
if (this.formNSFW) count++
|
|
|
|
if (this.formPublishedDateRange) count++
|
|
|
|
if (this.formDurationRange) count++
|
|
|
|
if (this.formCategoryOneOf) count++
|
|
|
|
if (this.formLicenceOneOf) count++
|
|
|
|
if (this.formLanguageOneOf) count++
|
|
|
|
if (this.formTagsAllOf && this.formTagsAllOf.length !== 0) count++
|
|
|
|
if (this.formTagsOneOf && this.formTagsOneOf.length !== 0) count++
|
|
|
|
|
|
|
|
return count
|
|
|
|
},
|
|
|
|
|
|
|
|
isChannelSearchDisabled () {
|
|
|
|
return this.countActiveFilters() > 0
|
|
|
|
},
|
|
|
|
|
|
|
|
scrollToResults () {
|
|
|
|
const anchor = document.getElementById('results-anchor')
|
|
|
|
if (anchor) anchor.scrollIntoView()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
</script>
|