mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
22 changed files with 480 additions and 192 deletions
@ -1,36 +1,34 @@ |
|||||
<span> |
<div class="selection"> |
||||
<div class="selection"> |
<input type="text" class="form-control" [disabled]="snapshot.isDisabled" (click)="open()" readonly (keydown)="onKeyDown($event)" #input |
||||
<input type="text" class="form-control" [disabled]="snapshot.isDisabled" (click)="open()" readonly (keydown)="onKeyDown($event)" #input |
autocomplete="off" |
||||
autocomplete="off" |
autocorrect="off" |
||||
autocorrect="off" |
autocapitalize="off"> |
||||
autocapitalize="off"> |
|
||||
|
<div class="control-dropdown-item" *ngIf="snapshot.selectedItem"> |
||||
|
<span class="truncate" *ngIf="!templateSelection">{{snapshot.selectedItem}}</span> |
||||
|
|
||||
<div class="control-dropdown-item" *ngIf="snapshot.selectedItem"> |
<ng-template *ngIf="templateSelection" [sqxTemplateWrapper]="templateSelection" [item]="snapshot.selectedItem"></ng-template> |
||||
<span class="truncate" *ngIf="!templateSelection">{{snapshot.selectedItem}}</span> |
</div> |
||||
|
|
||||
<ng-template *ngIf="templateSelection" [sqxTemplateWrapper]="templateSelection" [item]="snapshot.selectedItem"></ng-template> |
|
||||
</div> |
|
||||
|
|
||||
<i class="icon-caret-down"></i> |
<i class="icon-caret-down"></i> |
||||
</div> |
</div> |
||||
|
|
||||
<div class="items-container"> |
<div class="items-container"> |
||||
<ng-container *sqxModal="dropdown"> |
<ng-container *sqxModal="dropdown"> |
||||
<div class="control-dropdown" [sqxAnchoredTo]="input" position="bottom-left"> |
<div class="control-dropdown" [sqxAnchoredTo]="input" position="bottom-left"> |
||||
<div *ngIf="canSearch" class="search-form"> |
<div *ngIf="canSearch" class="search-form"> |
||||
<input class="form-control search" [formControl]="queryInput" [disabled]="snapshot.isDisabled" placeholder="Search" (keydown)="onKeyDown($event)" sqxFocusOnInit /> |
<input class="form-control search" [formControl]="queryInput" [disabled]="snapshot.isDisabled" placeholder="Search" (keydown)="onKeyDown($event)" sqxFocusOnInit /> |
||||
</div> |
</div> |
||||
|
|
||||
<div class="control-dropdown-items" #container> |
<div class="control-dropdown-items" #container> |
||||
<div *ngFor="let item of snapshot.suggestedItems; let i = index;" class="control-dropdown-item control-dropdown-item-selectable" [class.active]="i === snapshot.selectedIndex" (mousedown)="selectIndexAndClose(i)" |
<div *ngFor="let item of snapshot.suggestedItems; let i = index;" class="control-dropdown-item control-dropdown-item-selectable" [class.active]="i === snapshot.selectedIndex" (mousedown)="selectIndexAndClose(i)" |
||||
[sqxScrollActive]="i === snapshot.selectedIndex" |
[sqxScrollActive]="i === snapshot.selectedIndex" |
||||
[sqxScrollContainer]="container"> |
[sqxScrollContainer]="container"> |
||||
<ng-container *ngIf="!templateItem">{{item}}</ng-container> |
<ng-container *ngIf="!templateItem">{{item}}</ng-container> |
||||
|
|
||||
<ng-template *ngIf="templateItem" [sqxTemplateWrapper]="templateItem" [item]="item" [index]="i" [context]="snapshot.query"></ng-template> |
<ng-template *ngIf="templateItem" [sqxTemplateWrapper]="templateItem" [item]="item" [index]="i" [context]="snapshot.query"></ng-template> |
||||
</div> |
|
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
</ng-container> |
</div> |
||||
</div> |
</ng-container> |
||||
</span> |
</div> |
||||
@ -1,36 +1,65 @@ |
|||||
<div class="form-control tags" [class.blank]="styleBlank" [class.gray]="styleGray" #form (click)="input.focus()" |
<div class="form-container"> |
||||
[class.single-line]="singleLine" |
<div class="form-control tags" #form (click)="input.focus()" |
||||
[class.focus]="snapshot.hasFocus" |
[class.blank]="styleBlank" |
||||
[class.disabled]="addInput.disabled" |
[class.gray]="styleGray" |
||||
[class.dashed]="dashed && !(snapshot.items.length > 0)"> |
[class.singleline]="singleLine" |
||||
<span class="item" *ngFor="let item of snapshot.items; let i = index" [class.disabled]="addInput.disabled"> |
[class.multiline]="!singleLine" |
||||
{{item}} <i class="icon-close" (click)="remove(i)"></i> |
[class.focus]="snapshot.hasFocus" |
||||
</span> |
[class.disabled]="addInput.disabled" |
||||
|
[class.dashed]="dashed && !(snapshot.items.length > 0)"> |
||||
|
|
||||
<input type="text" class="blank" #input |
<span class="item" *ngFor="let item of snapshot.items; let i = index" [class.disabled]="addInput.disabled"> |
||||
(blur)="markTouched()" |
{{item}} <i class="icon-close" (click)="remove(i)"></i> |
||||
(copy)="onCopy($event)" |
</span> |
||||
(cut)="onCut($event)" |
|
||||
(focus)="focus()" |
|
||||
(keydown)="onKeyDown($event)" |
|
||||
(paste)="onPaste($event)" |
|
||||
[name]="inputName" [placeholder]="placeholder" |
|
||||
autocomplete="off" |
|
||||
autocorrect="off" |
|
||||
autocapitalize="off" |
|
||||
spellcheck="false" |
|
||||
[formControl]="addInput"> |
|
||||
</div> |
|
||||
|
|
||||
<ng-container *sqxModal="snapshot.suggestedItems.length > 0"> |
<input type="text" class="blank text-input" #input |
||||
<div class="control-dropdown" [sqxAnchoredTo]="form" position="bottom-left" #container @fade> |
(blur)="markTouched()" |
||||
<div *ngFor="let item of snapshot.suggestedItems; let i = index" class="control-dropdown-item control-dropdown-item-selectable" |
(copy)="onCopy($event)" |
||||
[class.active]="i === snapshot.suggestedIndex" |
(cut)="onCut($event)" |
||||
(mousedown)="selectValue(item)" |
(focus)="focus()" |
||||
(mouseover)="selectIndex(i)" |
(keydown)="onKeyDown($event)" |
||||
[sqxScrollActive]="i === snapshot.suggestedIndex" |
(paste)="onPaste($event)" |
||||
[sqxScrollContainer]="container"> |
[name]="inputName" [placeholder]="placeholder" |
||||
<ng-container>{{item}}</ng-container> |
autocomplete="off" |
||||
</div> |
autocorrect="off" |
||||
|
autocapitalize="off" |
||||
|
spellcheck="false" |
||||
|
[formControl]="addInput"> |
||||
|
</div> |
||||
|
|
||||
|
<div class="btn btn-sm" (click)="suggestionsModal.show()" sqxStopClick *ngIf="suggestionsSorted.length > 0"> |
||||
|
<i class="icon-caret-down"></i> |
||||
</div> |
</div> |
||||
</ng-container> |
|
||||
|
<ng-container *sqxModal="snapshot.suggestedItems.length > 0"> |
||||
|
<div class="control-dropdown" [sqxAnchoredTo]="form" position="bottom-left" #container @fade> |
||||
|
<div *ngFor="let item of snapshot.suggestedItems; let i = index" class="control-dropdown-item control-dropdown-item-selectable" |
||||
|
[class.active]="i === snapshot.suggestedIndex" |
||||
|
(mousedown)="selectValue(item)" |
||||
|
(mouseover)="selectIndex(i)" |
||||
|
[sqxScrollActive]="i === snapshot.suggestedIndex" |
||||
|
[sqxScrollContainer]="container"> |
||||
|
<ng-container>{{item}}</ng-container> |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-container *ngIf="snapshot.suggestedItems.length === 0 && suggestionsSorted.length > 0"> |
||||
|
<ng-container *sqxModal="suggestionsModal"> |
||||
|
<div class="control-dropdown suggestions-dropdown" [sqxAnchoredTo]="form" position="bottom-left" @fade> |
||||
|
<div class="row"> |
||||
|
<div class=" col-6" *ngFor="let item of suggestionsSorted; let i = index"> |
||||
|
<div class="form-check form-check-inline"> |
||||
|
<input class="form-check-input" type="checkbox" id="tag_{{i}}" |
||||
|
[ngModel]="isSelected(item)" |
||||
|
(ngModelChange)="toggleValue($event, item)" /> |
||||
|
<label class="form-check-label truncate" for="tag_{{i}}"> |
||||
|
{{item.name}} |
||||
|
</label> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</ng-container> |
||||
|
</div> |
||||
@ -0,0 +1,173 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core'; |
||||
|
import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; |
||||
|
|
||||
|
import { |
||||
|
AppsState, |
||||
|
ContentDto, |
||||
|
ContentsService, |
||||
|
Converter, |
||||
|
getContentValue, |
||||
|
LanguageDto, |
||||
|
StatefulControlComponent, |
||||
|
TagValue, |
||||
|
UIOptions |
||||
|
} from '@app/shared/internal'; |
||||
|
|
||||
|
export const SQX_REFERENCES_TAGS_CONTROL_VALUE_ACCESSOR: any = { |
||||
|
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReferencesTagsComponent), multi: true |
||||
|
}; |
||||
|
|
||||
|
const NO_EMIT = { emitEvent: false }; |
||||
|
|
||||
|
class TagsConverter implements Converter { |
||||
|
public suggestions: ReadonlyArray<TagValue> = []; |
||||
|
|
||||
|
constructor(language: LanguageDto, contents: ReadonlyArray<ContentDto>) { |
||||
|
this.suggestions = this.createTags(language, contents); |
||||
|
} |
||||
|
|
||||
|
public convertInput(input: string) { |
||||
|
const result = this.suggestions.find(x => x.name === input); |
||||
|
|
||||
|
return result || null; |
||||
|
} |
||||
|
|
||||
|
public convertValue(value: any) { |
||||
|
const result = this.suggestions.find(x => x.id === value); |
||||
|
|
||||
|
return result || null; |
||||
|
} |
||||
|
|
||||
|
private createTags(language: LanguageDto, contents: ReadonlyArray<ContentDto>): ReadonlyArray<TagValue> { |
||||
|
if (contents.length === 0) { |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
const values = contents.map(content => { |
||||
|
const name = |
||||
|
content.referenceFields |
||||
|
.map(f => getContentValue(content, language, f, false)) |
||||
|
.map(v => v.formatted || 'No value') |
||||
|
.filter(v => !!v) |
||||
|
.join(', '); |
||||
|
|
||||
|
return new TagValue(content.id, name, content.id); |
||||
|
}); |
||||
|
|
||||
|
return values; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
interface State { |
||||
|
converter: TagsConverter; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-references-tags', |
||||
|
template: ` |
||||
|
<sqx-tag-editor placeholder=", to add reference" [converter]="snapshot.converter" [formControl]="selectionControl" |
||||
|
[suggestedValues]="snapshot.converter.suggestions"> |
||||
|
</sqx-tag-editor>`, |
||||
|
styles: [ |
||||
|
'.truncate { min-height: 1.5rem; }' |
||||
|
], |
||||
|
providers: [SQX_REFERENCES_TAGS_CONTROL_VALUE_ACCESSOR], |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class ReferencesTagsComponent extends StatefulControlComponent<State, ReadonlyArray<string>> implements OnChanges { |
||||
|
private itemCount: number; |
||||
|
private contentItems: ReadonlyArray<ContentDto> | null = null; |
||||
|
|
||||
|
@Input() |
||||
|
public schemaId: string; |
||||
|
|
||||
|
@Input() |
||||
|
public language: LanguageDto; |
||||
|
|
||||
|
public get isValid() { |
||||
|
return !!this.schemaId && !!this.language; |
||||
|
} |
||||
|
|
||||
|
public selectionControl = new FormControl([]); |
||||
|
|
||||
|
constructor(changeDetector: ChangeDetectorRef, uiOptions: UIOptions, |
||||
|
private readonly appsState: AppsState, |
||||
|
private readonly contentsService: ContentsService |
||||
|
) { |
||||
|
super(changeDetector, { converter: new TagsConverter(null!, []) }); |
||||
|
|
||||
|
this.itemCount = uiOptions.get('referencesDropdownItemCount'); |
||||
|
|
||||
|
this.own( |
||||
|
this.selectionControl.valueChanges |
||||
|
.subscribe((value: string[]) => { |
||||
|
if (value && value.length > 0) { |
||||
|
this.callTouched(); |
||||
|
this.callChange(value); |
||||
|
} else { |
||||
|
this.callTouched(); |
||||
|
this.callChange(null); |
||||
|
} |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
public ngOnChanges(changes: SimpleChanges) { |
||||
|
if (changes['schemaId']) { |
||||
|
this.resetState(); |
||||
|
|
||||
|
if (this.isValid) { |
||||
|
this.contentsService.getContents(this.appsState.appName, this.schemaId, this.itemCount, 0) |
||||
|
.subscribe(contents => { |
||||
|
this.contentItems = contents.items; |
||||
|
|
||||
|
this.resetConverterState(); |
||||
|
}, () => { |
||||
|
this.contentItems = null; |
||||
|
|
||||
|
this.resetConverterState(); |
||||
|
}); |
||||
|
} else { |
||||
|
this.contentItems = null; |
||||
|
|
||||
|
this.resetConverterState(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public setDisabledState(isDisabled: boolean) { |
||||
|
if (isDisabled) { |
||||
|
this.selectionControl.disable(); |
||||
|
} else if (this.isValid) { |
||||
|
this.selectionControl.enable(); |
||||
|
} |
||||
|
|
||||
|
super.setDisabledState(isDisabled); |
||||
|
} |
||||
|
|
||||
|
public writeValue(obj: ReadonlyArray<string>) { |
||||
|
this.selectionControl.setValue(obj, NO_EMIT); |
||||
|
} |
||||
|
|
||||
|
private resetConverterState() { |
||||
|
let converter: TagsConverter; |
||||
|
|
||||
|
if (this.isValid && this.contentItems && this.contentItems.length > 0) { |
||||
|
converter = new TagsConverter(this.language, this.contentItems); |
||||
|
|
||||
|
this.selectionControl.enable(); |
||||
|
} else { |
||||
|
converter = new TagsConverter(null!, []); |
||||
|
|
||||
|
this.selectionControl.disable(); |
||||
|
} |
||||
|
|
||||
|
this.next({ converter }); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue