]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add ability to filter by host in search page
authorChocobozzz <me@florianbigard.com>
Thu, 29 Jul 2021 13:19:22 +0000 (15:19 +0200)
committerChocobozzz <me@florianbigard.com>
Thu, 29 Jul 2021 13:19:22 +0000 (15:19 +0200)
client/src/app/+search/search-filters.component.html
client/src/app/+search/search-filters.component.ts
client/src/app/+search/search.component.html
client/src/app/+search/search.component.scss
client/src/app/+search/search.component.ts
client/src/app/shared/form-validators/host-validators.ts
client/src/app/shared/shared-search/advanced-search.model.ts
client/src/app/shared/shared-search/search.service.ts

index 421bc7f6f1bd8a758e66bb892473d6be1c61fdf9..4b87a210209e199048820510aaea8ab92d87141c 100644 (file)
@@ -63,7 +63,7 @@
         </div>
 
         <div class="peertube-radio-container" *ngFor="let date of publishedDateRanges">
-          <input type="radio" (change)="onInputUpdated()" name="publishedDateRange" [id]="date.id" [value]="date.id" [(ngModel)]="publishedDateRange">
+          <input type="radio" (change)="onDurationOrPublishedUpdated()" name="publishedDateRange" [id]="date.id" [value]="date.id" [(ngModel)]="publishedDateRange">
           <label [for]="date.id" class="radio">{{ date.label }}</label>
         </div>
       </div>
@@ -79,7 +79,7 @@
         <div class="row">
           <div class="pl-0 col-sm-6">
             <input
-              (change)="onInputUpdated()"
+              (change)="onDurationOrPublishedUpdated()"
               (keydown.enter)="$event.preventDefault()"
               type="text" id="original-publication-after" name="original-publication-after"
               i18n-placeholder placeholder="After..."
@@ -89,7 +89,7 @@
           </div>
           <div class="pr-0 col-sm-6">
             <input
-              (change)="onInputUpdated()"
+              (change)="onDurationOrPublishedUpdated()"
               (keydown.enter)="$event.preventDefault()"
               type="text" id="original-publication-before" name="original-publication-before"
               i18n-placeholder placeholder="Before..."
         </div>
 
         <div class="peertube-radio-container" *ngFor="let duration of durationRanges">
-          <input type="radio" (change)="onInputUpdated()" name="durationRange" [id]="duration.id" [value]="duration.id" [(ngModel)]="durationRange">
+          <input type="radio" (change)="onDurationOrPublishedUpdated()" name="durationRange" [id]="duration.id" [value]="duration.id" [(ngModel)]="durationRange">
           <label [for]="duration.id" class="radio">{{ duration.label }}</label>
         </div>
       </div>
         <my-select-tags name="tagsOneOf" labelForId="tagsOneOf" id="tagsOneOf" [(ngModel)]="advancedSearch.tagsOneOf"></my-select-tags>
       </div>
 
+      <div class="form-group">
+        <label i18n for="host">PeerTube instance host</label>
+
+        <input (change)="onDurationOrPublishedUpdated()" (keydown.enter)="$event.preventDefault()" type="text" id="host" name="host"
+          placeholder="example.com" [(ngModel)]="advancedSearch.host" class="form-control"
+        >
+      </div>
+
       <div class="form-group" *ngIf="isSearchTargetEnabled()">
         <div class="radio-label label-container">
           <label i18n>Search target</label>
index afa523b915ec30295c6081fcb5c7a15e67829d53..5972ba553201408f438d72b7a8f0cdb5b19e1137 100644 (file)
@@ -108,14 +108,14 @@ export class SearchFiltersComponent implements OnInit {
     this.loadOriginallyPublishedAtYears()
   }
 
-  onInputUpdated () {
+  onDurationOrPublishedUpdated () {
     this.updateModelFromDurationRange()
     this.updateModelFromPublishedRange()
     this.updateModelFromOriginallyPublishedAtYears()
   }
 
   formUpdated () {
-    this.onInputUpdated()
+    this.onDurationOrPublishedUpdated()
     this.filtered.emit(this.advancedSearch)
   }
 
@@ -127,7 +127,7 @@ export class SearchFiltersComponent implements OnInit {
     this.durationRange = undefined
     this.publishedDateRange = undefined
 
-    this.onInputUpdated()
+    this.onDurationOrPublishedUpdated()
   }
 
   resetField (fieldName: string, value?: any) {
@@ -136,7 +136,7 @@ export class SearchFiltersComponent implements OnInit {
 
   resetLocalField (fieldName: string, value?: any) {
     this[fieldName] = value
-    this.onInputUpdated()
+    this.onDurationOrPublishedUpdated()
   }
 
   resetOriginalPublicationYears () {
index b28abca6ac002eed4b538f2ad2cc297a86ee33a3..dc8b4d5951590898fabde323b23d699f8078aab1 100644 (file)
@@ -24,6 +24,8 @@
 
     <div class="results-filter collapse-transition" [ngbCollapse]="isSearchFilterCollapsed">
       <my-search-filters [advancedSearch]="advancedSearch" (filtered)="onFiltered()"></my-search-filters>
+
+      <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
     </div>
   </div>
 
index fca704d27762a3d84239216418e1e14465938bb1..b521825e56915037909d83b7be33a09bacac8d31 100644 (file)
   padding: 40px;
 }
 
+.alert-danger {
+  margin-top: 10px;
+}
+
 .results-header {
   font-size: 16px;
   padding-bottom: 20px;
index 235bbfa4c1660bdfc2dd13af3f48426b2e5c8391..250062e0cae1624155cb58447d95b05caed12576 100644 (file)
@@ -4,6 +4,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { AuthService, HooksService, MetaService, Notifier, ServerService, User, UserService } from '@app/core'
 import { immutableAssign } from '@app/helpers'
+import { validateHost } from '@app/shared/form-validators/host-validators'
 import { Video, VideoChannel } from '@app/shared/shared-main'
 import { AdvancedSearch, SearchService } from '@app/shared/shared-search'
 import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature'
@@ -16,6 +17,8 @@ import { HTMLServerConfig, SearchTargetType } from '@shared/models'
   templateUrl: './search.component.html'
 })
 export class SearchComponent implements OnInit, OnDestroy {
+  error: string
+
   results: (Video | VideoChannel)[] = []
 
   pagination = {
@@ -89,8 +92,10 @@ export class SearchComponent implements OnInit, OnDestroy {
           this.advancedSearch.searchTarget = this.getDefaultSearchTarget()
         }
 
-        // Don't hide filters if we have some of them AND the user just came on the webpage
-        this.isSearchFilterCollapsed = this.isInitialLoad === false || !this.advancedSearch.containsValues()
+        this.error = this.checkFieldsAndGetError()
+
+        // Don't hide filters if we have some of them AND the user just came on the webpage, or we have an error
+        this.isSearchFilterCollapsed = !this.error && (this.isInitialLoad === false || !this.advancedSearch.containsValues())
         this.isInitialLoad = false
 
         this.search()
@@ -126,6 +131,9 @@ export class SearchComponent implements OnInit, OnDestroy {
   }
 
   search () {
+    this.error = this.checkFieldsAndGetError()
+    if (this.error) return
+
     this.isSearching = true
 
     forkJoin([
@@ -280,7 +288,7 @@ export class SearchComponent implements OnInit, OnDestroy {
     const params = {
       search: this.currentSearch,
       componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.channelsPerPage }),
-      searchTarget: this.advancedSearch.searchTarget
+      advancedSearch: this.advancedSearch
     }
 
     return this.hooks.wrapObsFun(
@@ -298,7 +306,7 @@ export class SearchComponent implements OnInit, OnDestroy {
     const params = {
       search: this.currentSearch,
       componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.playlistsPerPage }),
-      searchTarget: this.advancedSearch.searchTarget
+      advancedSearch: this.advancedSearch
     }
 
     return this.hooks.wrapObsFun(
@@ -319,4 +327,12 @@ export class SearchComponent implements OnInit, OnDestroy {
 
     return 'local'
   }
+
+  private checkFieldsAndGetError () {
+    if (this.advancedSearch.host && !validateHost(this.advancedSearch.host)) {
+      return $localize`PeerTube instance host filter is invalid`
+    }
+
+    return undefined
+  }
 }
index d750113ef3bec1113a5a7ed2a08512726cb626f4..6f410a50a7c5b8aa8e8e3a0959c686145ae2ed85 100644 (file)
@@ -1,7 +1,7 @@
 import { AbstractControl, ValidatorFn, Validators } from '@angular/forms'
 import { BuildFormValidator } from './form-validator.model'
 
-function validateHost (value: string) {
+export function validateHost (value: string) {
   // Thanks to http://stackoverflow.com/a/106223
   const HOST_REGEXP = new RegExp(
     '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$'
@@ -10,7 +10,7 @@ function validateHost (value: string) {
   return HOST_REGEXP.test(value)
 }
 
-function validateHandle (value: string) {
+export function validateHandle (value: string) {
   if (!value) return false
 
   return value.includes('@')
index 2c83f53b64c3a12d611b2a10ee2478b76f23fa83..9c55f6cd853e0b68d9560911718108e39f707427 100644 (file)
@@ -1,4 +1,11 @@
-import { BooleanBothQuery, BooleanQuery, SearchTargetType, VideosSearchQuery } from '@shared/models'
+import {
+  BooleanBothQuery,
+  BooleanQuery,
+  SearchTargetType,
+  VideoChannelsSearchQuery,
+  VideoPlaylistsSearchQuery,
+  VideosSearchQuery
+} from '@shared/models'
 
 export class AdvancedSearch {
   startDate: string // ISO 8601
@@ -23,6 +30,8 @@ export class AdvancedSearch {
 
   isLive: BooleanQuery
 
+  host: string
+
   sort: string
 
   searchTarget: SearchTargetType
@@ -45,6 +54,8 @@ export class AdvancedSearch {
 
     isLive?: BooleanQuery
 
+    host?: string
+
     durationMin?: string
     durationMax?: string
     sort?: string
@@ -68,6 +79,8 @@ export class AdvancedSearch {
     this.durationMin = parseInt(options.durationMin, 10)
     this.durationMax = parseInt(options.durationMax, 10)
 
+    this.host = options.host || undefined
+
     this.searchTarget = options.searchTarget || undefined
 
     if (isNaN(this.durationMin)) this.durationMin = undefined
@@ -101,6 +114,7 @@ export class AdvancedSearch {
     this.durationMin = undefined
     this.durationMax = undefined
     this.isLive = undefined
+    this.host = undefined
 
     this.sort = '-match'
   }
@@ -120,12 +134,13 @@ export class AdvancedSearch {
       durationMin: this.durationMin,
       durationMax: this.durationMax,
       isLive: this.isLive,
+      host: this.host,
       sort: this.sort,
       searchTarget: this.searchTarget
     }
   }
 
-  toAPIObject (): VideosSearchQuery {
+  toVideosAPIObject (): VideosSearchQuery {
     let isLive: boolean
     if (this.isLive) isLive = this.isLive === 'true'
 
@@ -142,12 +157,27 @@ export class AdvancedSearch {
       tagsAllOf: this.tagsAllOf,
       durationMin: this.durationMin,
       durationMax: this.durationMax,
+      host: this.host,
       isLive,
       sort: this.sort,
       searchTarget: this.searchTarget
     }
   }
 
+  toPlaylistAPIObject (): VideoPlaylistsSearchQuery {
+    return {
+      host: this.host,
+      searchTarget: this.searchTarget
+    }
+  }
+
+  toChannelAPIObject (): VideoChannelsSearchQuery {
+    return {
+      host: this.host,
+      searchTarget: this.searchTarget
+    }
+  }
+
   size () {
     let acc = 0
 
index ad258f5e5b89c19579ad462619598f6d47ffd633..2c26eb2e55afa4ac5147148d833b2c09aa0bafac 100644 (file)
@@ -7,7 +7,6 @@ import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/sha
 import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
 import {
   ResultList,
-  SearchTargetType,
   Video as VideoServerModel,
   VideoChannel as VideoChannelServerModel,
   VideoPlaylist as VideoPlaylistServerModel
@@ -33,8 +32,8 @@ export class SearchService {
   }
 
   searchVideos (parameters: {
-    search: string,
-    componentPagination?: ComponentPaginationLight,
+    search: string
+    componentPagination?: ComponentPaginationLight
     advancedSearch?: AdvancedSearch
   }): Observable<ResultList<Video>> {
     const { search, componentPagination, advancedSearch } = parameters
@@ -52,7 +51,7 @@ export class SearchService {
     if (search) params = params.append('search', search)
 
     if (advancedSearch) {
-      const advancedSearchObject = advancedSearch.toAPIObject()
+      const advancedSearchObject = advancedSearch.toVideosAPIObject()
       params = this.restService.addObjectParams(params, advancedSearchObject)
     }
 
@@ -65,11 +64,11 @@ export class SearchService {
   }
 
   searchVideoChannels (parameters: {
-    search: string,
-    searchTarget?: SearchTargetType,
+    search: string
+    advancedSearch?: AdvancedSearch
     componentPagination?: ComponentPaginationLight
   }): Observable<ResultList<VideoChannel>> {
-    const { search, componentPagination, searchTarget } = parameters
+    const { search, advancedSearch, componentPagination } = parameters
 
     const url = SearchService.BASE_SEARCH_URL + 'video-channels'
 
@@ -82,8 +81,9 @@ export class SearchService {
     params = this.restService.addRestGetParams(params, pagination)
     params = params.append('search', search)
 
-    if (searchTarget) {
-      params = params.append('searchTarget', searchTarget as string)
+    if (advancedSearch) {
+      const advancedSearchObject = advancedSearch.toChannelAPIObject()
+      params = this.restService.addObjectParams(params, advancedSearchObject)
     }
 
     return this.authHttp
@@ -95,11 +95,11 @@ export class SearchService {
   }
 
   searchVideoPlaylists (parameters: {
-    search: string,
-    searchTarget?: SearchTargetType,
+    search: string
+    advancedSearch?: AdvancedSearch
     componentPagination?: ComponentPaginationLight
   }): Observable<ResultList<VideoPlaylist>> {
-    const { search, componentPagination, searchTarget } = parameters
+    const { search, advancedSearch, componentPagination } = parameters
 
     const url = SearchService.BASE_SEARCH_URL + 'video-playlists'
 
@@ -112,8 +112,9 @@ export class SearchService {
     params = this.restService.addRestGetParams(params, pagination)
     params = params.append('search', search)
 
-    if (searchTarget) {
-      params = params.append('searchTarget', searchTarget as string)
+    if (advancedSearch) {
+      const advancedSearchObject = advancedSearch.toPlaylistAPIObject()
+      params = this.restService.addObjectParams(params, advancedSearchObject)
     }
 
     return this.authHttp