</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>
<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..."
</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>
this.loadOriginallyPublishedAtYears()
}
- onInputUpdated () {
+ onDurationOrPublishedUpdated () {
this.updateModelFromDurationRange()
this.updateModelFromPublishedRange()
this.updateModelFromOriginallyPublishedAtYears()
}
formUpdated () {
- this.onInputUpdated()
+ this.onDurationOrPublishedUpdated()
this.filtered.emit(this.advancedSearch)
}
this.durationRange = undefined
this.publishedDateRange = undefined
- this.onInputUpdated()
+ this.onDurationOrPublishedUpdated()
}
resetField (fieldName: string, value?: any) {
resetLocalField (fieldName: string, value?: any) {
this[fieldName] = value
- this.onInputUpdated()
+ this.onDurationOrPublishedUpdated()
}
resetOriginalPublicationYears () {
<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>
padding: 40px;
}
+.alert-danger {
+ margin-top: 10px;
+}
+
.results-header {
font-size: 16px;
padding-bottom: 20px;
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'
templateUrl: './search.component.html'
})
export class SearchComponent implements OnInit, OnDestroy {
+ error: string
+
results: (Video | VideoChannel)[] = []
pagination = {
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()
}
search () {
+ this.error = this.checkFieldsAndGetError()
+ if (this.error) return
+
this.isSearching = true
forkJoin([
const params = {
search: this.currentSearch,
componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.channelsPerPage }),
- searchTarget: this.advancedSearch.searchTarget
+ advancedSearch: this.advancedSearch
}
return this.hooks.wrapObsFun(
const params = {
search: this.currentSearch,
componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.playlistsPerPage }),
- searchTarget: this.advancedSearch.searchTarget
+ advancedSearch: this.advancedSearch
}
return this.hooks.wrapObsFun(
return 'local'
}
+
+ private checkFieldsAndGetError () {
+ if (this.advancedSearch.host && !validateHost(this.advancedSearch.host)) {
+ return $localize`PeerTube instance host filter is invalid`
+ }
+
+ return undefined
+ }
}
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])$'
return HOST_REGEXP.test(value)
}
-function validateHandle (value: string) {
+export function validateHandle (value: string) {
if (!value) return false
return value.includes('@')
-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
isLive: BooleanQuery
+ host: string
+
sort: string
searchTarget: SearchTargetType
isLive?: BooleanQuery
+ host?: string
+
durationMin?: string
durationMax?: string
sort?: string
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
this.durationMin = undefined
this.durationMax = undefined
this.isLive = undefined
+ this.host = undefined
this.sort = '-match'
}
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'
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
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import {
ResultList,
- SearchTargetType,
Video as VideoServerModel,
VideoChannel as VideoChannelServerModel,
VideoPlaylist as VideoPlaylistServerModel
}
searchVideos (parameters: {
- search: string,
- componentPagination?: ComponentPaginationLight,
+ search: string
+ componentPagination?: ComponentPaginationLight
advancedSearch?: AdvancedSearch
}): Observable<ResultList<Video>> {
const { search, componentPagination, advancedSearch } = parameters
if (search) params = params.append('search', search)
if (advancedSearch) {
- const advancedSearchObject = advancedSearch.toAPIObject()
+ const advancedSearchObject = advancedSearch.toVideosAPIObject()
params = this.restService.addObjectParams(params, advancedSearchObject)
}
}
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'
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
}
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'
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