for (const k of Object.keys(obj)) {
if (k === 'sort') continue // Exception
- if (obj[k] !== undefined) return true
+ if (obj[k] !== undefined && obj[k] !== '') return true
}
return false
for (const k of Object.keys(obj)) {
if (k === 'sort') continue // Exception
- if (obj[k] !== undefined) acc++
+ if (obj[k] !== undefined && obj[k] !== '') acc++
}
return acc
<div class="row">
<div class="col-lg-4 col-md-6 col-xs-12">
<div class="form-group">
- <div i18n class="radio-label">Sort</div>
+ <div class="radio-label label-container">
+ <label i18n>Sort</label>
+ <button i18n class="reset-button reset-button-small" (click)="resetField('sort', '-match')" *ngIf="advancedSearch.sort !== '-match'">
+ Reset
+ </button>
+ </div>
<div class="peertube-radio-container" *ngFor="let sort of sorts">
<input type="radio" name="sort" [id]="sort.id" [value]="sort.id" [(ngModel)]="advancedSearch.sort">
</div>
<div class="form-group">
- <div i18n class="radio-label">Published date</div>
+ <div class="radio-label label-container">
+ <label i18n>Published date</label>
+ <button i18n class="reset-button reset-button-small" (click)="resetLocalField('publishedDateRange')" *ngIf="publishedDateRange !== undefined">
+ Reset
+ </button>
+ </div>
<div class="peertube-radio-container" *ngFor="let date of publishedDateRanges">
- <input type="radio" name="publishedDateRange" [id]="date.id" [value]="date.id" [(ngModel)]="publishedDateRange">
+ <input type="radio" (change)="inputUpdated()" name="publishedDateRange" [id]="date.id" [value]="date.id" [(ngModel)]="publishedDateRange">
<label [for]="date.id" class="radio">{{ date.label }}</label>
</div>
</div>
<div class="form-group">
- <label i18n for="original-publication-after">Original publication year</label>
+ <div class="label-container">
+ <label i18n for="original-publication-after">Original publication year</label>
+ <button i18n class="reset-button reset-button-small" (click)="resetOriginalPublicationYears()" *ngIf="originallyPublishedStartYear || originallyPublishedEndYear">
+ Reset
+ </button>
+ </div>
<div class="row">
<div class="col-sm-6">
<input
+ (change)="inputUpdated()"
+ (keydown.enter)="$event.preventDefault()"
type="text" id="original-publication-after" name="original-publication-after"
i18n-placeholder placeholder="After..."
[(ngModel)]="originallyPublishedStartYear"
</div>
<div class="col-sm-6">
<input
+ (change)="inputUpdated()"
+ (keydown.enter)="$event.preventDefault()"
type="text" id="original-publication-before" name="original-publication-before"
i18n-placeholder placeholder="Before..."
[(ngModel)]="originallyPublishedEndYear"
</div>
<div class="form-group">
- <div i18n class="radio-label">Duration</div>
+ <div class="radio-label label-container">
+ <label i18n>Duration</label>
+ <button i18n class="reset-button reset-button-small" (click)="resetLocalField('durationRange')" *ngIf="durationRange !== undefined">
+ Reset
+ </button>
+ </div>
<div class="peertube-radio-container" *ngFor="let duration of durationRanges">
- <input type="radio" name="durationRange" [id]="duration.id" [value]="duration.id" [(ngModel)]="durationRange">
+ <input type="radio" (change)="inputUpdated()" name="durationRange" [id]="duration.id" [value]="duration.id" [(ngModel)]="durationRange">
<label [for]="duration.id" class="radio">{{ duration.label }}</label>
</div>
</div>
<div class="form-group">
- <div i18n class="radio-label">Display sensitive content</div>
+ <div class="radio-label label-container">
+ <label i18n>Display sensitive content</label>
+ <button i18n class="reset-button reset-button-small" (click)="resetField('nsfw')" *ngIf="advancedSearch.nsfw !== undefined">
+ Reset
+ </button>
+ </div>
<div class="peertube-radio-container">
<input type="radio" name="sensitiveContent" id="sensitiveContentYes" value="both" [(ngModel)]="advancedSearch.nsfw">
<div class="col-lg-4 col-md-6 col-xs-12">
<div class="form-group">
<label i18n for="category">Category</label>
+ <button i18n class="reset-button reset-button-small" (click)="resetField('categoryOneOf')" *ngIf="advancedSearch.categoryOneOf !== undefined">
+ Reset
+ </button>
<div class="peertube-select-container">
<select id="category" name="category" [(ngModel)]="advancedSearch.categoryOneOf">
- <option></option>
+ <option [value]="undefined" i18n>Any or no category set</option>
<option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
</select>
</div>
<div class="form-group">
<label i18n for="licence">Licence</label>
+ <button i18n class="reset-button reset-button-small" (click)="resetField('licenceOneOf')" *ngIf="advancedSearch.licenceOneOf !== undefined">
+ Reset
+ </button>
<div class="peertube-select-container">
<select id="licence" name="licence" [(ngModel)]="advancedSearch.licenceOneOf">
- <option></option>
+ <option [value]="undefined" i18n>Any or no license set</option>
<option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
</select>
</div>
<div class="form-group">
<label i18n for="language">Language</label>
+ <button i18n class="reset-button reset-button-small" (click)="resetField('languageOneOf')" *ngIf="advancedSearch.languageOneOf !== undefined">
+ Reset
+ </button>
<div class="peertube-select-container">
<select id="language" name="language" [(ngModel)]="advancedSearch.languageOneOf">
- <option></option>
+ <option [value]="undefined" i18n>Any or no language set</option>
<option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
</select>
</div>
<div class="col-lg-4 col-md-6 col-xs-12">
<div class="form-group">
<label i18n for="tagsAllOf">All of these tags</label>
- <input type="text" name="tagsAllOf" id="tagsAllOf" [(ngModel)]="advancedSearch.tagsAllOf" />
+ <button i18n class="reset-button reset-button-small" (click)="resetField('tagsAllOf')" *ngIf="advancedSearch.tagsAllOf">
+ Reset
+ </button>
+ <tag-input
+ [(ngModel)]="advancedSearch.tagsAllOf" name="tagsAllOf" id="tagsAllOf"
+ [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
+ i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a tag"
+ maxItems="5" modelAsStrings="true"
+ ></tag-input>
</div>
<div class="form-group">
<label i18n for="tagsOneOf">One of these tags</label>
- <input type="text" name="tagsOneOf" id="tagsOneOf" [(ngModel)]="advancedSearch.tagsOneOf" />
+ <button i18n class="reset-button reset-button-small" (click)="resetField('tagsOneOf')" *ngIf="advancedSearch.tagsOneOf">
+ Reset
+ </button>
+ <tag-input
+ [(ngModel)]="advancedSearch.tagsOneOf" name="tagsOneOf" id="tagsOneOf"
+ [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
+ i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a tag"
+ maxItems="5" modelAsStrings="true"
+ ></tag-input>
</div>
</div>
</div>
<div class="submit-button">
+ <button i18n class="reset-button" (click)="reset()" *ngIf="advancedSearch.size()">
+ Reset
+ </button>
+
<input type="submit" i18n-value value="Filter">
</div>
</form>
.peertube-select-container {
@include peertube-select-container(auto);
+
+ margin-bottom: 1rem;
}
.form-group {
.submit-button {
text-align: right;
-}
\ No newline at end of file
+}
+
+.reset-button {
+ @include peertube-button;
+
+ font-weight: $font-semibold;
+ display: inline-block;
+ padding: 0 10px 0 10px;
+ white-space: nowrap;
+ background: transparent;
+
+ margin-right: 1rem;
+}
+
+.reset-button-small {
+ font-size: 80%;
+ height: unset;
+ line-height: unset;
+ margin: unset;
+ margin-bottom: 0.5rem;
+}
+
+.label-container {
+ display: flex;
+ white-space: nowrap;
+}
+
+::ng-deep {
+ .ng2-tag-input {
+ border: none !important;
+ }
+
+ .ng2-tags-container {
+ display: flex;
+ align-items: center;
+ border: 1px solid #C6C6C6;
+ border-radius: 3px;
+ padding: 5px !important;
+ height: max-content;
+ }
+
+ tag-input-form {
+ input {
+ height: 30px !important;
+ font-size: 12px !important;
+
+ background-color: var(--mainBackgroundColor) !important;
+ color: var(--mainForegroundColor) !important;
+ }
+ }
+
+ tag {
+ background-color: $grey-background-color !important;
+ color: #000 !important;
+ border-radius: 3px !important;
+ font-size: 12px !important;
+ height: 30px !important;
+ line-height: 30px !important;
+ margin: 0 5px 0 0 !important;
+ cursor: default !important;
+ padding: 0 8px 0 10px !important;
+
+ div {
+ height: 100% !important;
+ }
+ }
+
+ delete-icon {
+ cursor: pointer !important;
+ height: auto !important;
+ vertical-align: middle !important;
+ padding-left: 6px !important;
+
+ svg {
+ position: relative;
+ top: -1px;
+ height: auto !important;
+ vertical-align: middle !important;
+
+ path {
+ fill: $grey-foreground-color !important;
+ }
+ }
+
+ &:hover {
+ transform: none !important;
+ }
+ }
+}
-import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
+import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'
+import { ValidatorFn } from '@angular/forms'
+import { VideoValidatorsService } from '@app/shared'
import { ServerService } from '@app/core'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { AdvancedSearch } from '@app/search/advanced-search.model'
videoLicences: VideoConstant<number>[] = []
videoLanguages: VideoConstant<string>[] = []
+ tagValidators: ValidatorFn[]
+ tagValidatorsMessages: { [ name: string ]: string }
+
publishedDateRanges: { id: string, label: string }[] = []
sorts: { id: string, label: string }[] = []
durationRanges: { id: string, label: string }[] = []
constructor (
private i18n: I18n,
+ private videoValidatorsService: VideoValidatorsService,
private serverService: ServerService
) {
+ this.tagValidators = this.videoValidatorsService.VIDEO_TAGS.VALIDATORS
+ this.tagValidatorsMessages = this.videoValidatorsService.VIDEO_TAGS.MESSAGES
this.publishedDateRanges = [
+ {
+ id: undefined,
+ label: this.i18n('Any')
+ },
{
id: 'today',
label: this.i18n('Today')
]
this.durationRanges = [
+ {
+ id: undefined,
+ label: this.i18n('Any')
+ },
{
id: 'short',
label: this.i18n('Short (< 4 min)')
this.loadOriginallyPublishedAtYears()
}
- formUpdated () {
+ inputUpdated () {
this.updateModelFromDurationRange()
this.updateModelFromPublishedRange()
this.updateModelFromOriginallyPublishedAtYears()
+ }
+ formUpdated () {
+ this.inputUpdated()
this.filtered.emit(this.advancedSearch)
}
this.advancedSearch.startDate = date.toISOString()
}
+ private reset () {
+ this.advancedSearch.reset()
+ this.durationRange = undefined
+ this.publishedDateRange = undefined
+ this.originallyPublishedStartYear = undefined
+ this.originallyPublishedEndYear = undefined
+ this.inputUpdated()
+ }
+
+ private resetField (fieldName: string, value?: any) {
+ this.advancedSearch[fieldName] = value
+ }
+
+ private resetLocalField (fieldName: string, value?: any) {
+ this[fieldName] = value
+ this.inputUpdated()
+ }
+
+ private resetOriginalPublicationYears () {
+ this.originallyPublishedStartYear = this.originallyPublishedEndYear = undefined
+ }
+
}
import { NgModule } from '@angular/core'
+import { TagInputModule } from 'ngx-chips'
import { SharedModule } from '../shared'
import { SearchComponent } from '@app/search/search.component'
import { SearchService } from '@app/search/search.service'
@NgModule({
imports: [
+ TagInputModule,
+
SearchRoutingModule,
SharedModule
],
],
exports: [
+ TagInputModule,
SearchComponent
],
border: 1px solid #C6C6C6;
border-radius: 3px;
padding: 5px !important;
- height: 40px;
+ height: max-content;
}
tag-input-form {
input {
height: 30px !important;
+ font-size: 12px !important;
background-color: var(--mainBackgroundColor) !important;
color: var(--mainForegroundColor) !important;
background-color: $grey-background-color !important;
color: #000 !important;
border-radius: 3px !important;
- font-size: 15px !important;
+ font-size: 12px !important;
height: 30px !important;
line-height: 30px !important;
margin: 0 5px 0 0 !important;
color: $grey-foreground-color;
margin-bottom: 10px;
font-weight: $font-semibold;
+ text-decoration: none;
}
@media screen and (max-width: $mobile-view) {