mirror of https://github.com/Squidex/squidex.git
21 changed files with 382 additions and 184 deletions
@ -0,0 +1,136 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { Types } from './types'; |
||||
|
|
||||
|
export class TagValue<T = any> { |
||||
|
public readonly lowerCaseName: string; |
||||
|
|
||||
|
constructor( |
||||
|
public readonly id: any, |
||||
|
public readonly name: string, |
||||
|
public readonly value: T |
||||
|
) { |
||||
|
this.lowerCaseName = name.toLowerCase(); |
||||
|
} |
||||
|
|
||||
|
public toString() { |
||||
|
return this.name; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export interface TagConverter { |
||||
|
convertInput(input: string): TagValue | null; |
||||
|
|
||||
|
convertValue(value: any): TagValue | null; |
||||
|
} |
||||
|
|
||||
|
export class IntConverter implements TagConverter { |
||||
|
private static ZERO = new TagValue(0, '0', 0); |
||||
|
|
||||
|
public static readonly INSTANCE: TagConverter = new IntConverter(); |
||||
|
|
||||
|
private constructor() {} |
||||
|
|
||||
|
public convertInput(input: string) { |
||||
|
if (input === '0') { |
||||
|
return IntConverter.ZERO; |
||||
|
} |
||||
|
|
||||
|
const parsed = parseInt(input, 10); |
||||
|
|
||||
|
if (parsed) { |
||||
|
return new TagValue(parsed, input, parsed); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public convertValue(value: any) { |
||||
|
if (Types.isNumber(value)) { |
||||
|
return new TagValue(value, `${value}`, value); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class FloatConverter implements TagConverter { |
||||
|
private static ZERO = new TagValue(0, '0', 0); |
||||
|
|
||||
|
public static readonly INSTANCE: TagConverter = new FloatConverter(); |
||||
|
|
||||
|
private constructor() {} |
||||
|
|
||||
|
public convertInput(input: string) { |
||||
|
if (input === '0') { |
||||
|
return FloatConverter.ZERO; |
||||
|
} |
||||
|
|
||||
|
const parsed = parseFloat(input); |
||||
|
|
||||
|
if (parsed) { |
||||
|
return new TagValue(parsed, input, parsed); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public convertValue(value: any) { |
||||
|
if (Types.isNumber(value)) { |
||||
|
return new TagValue(value, `${value}`, value); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class StringConverter implements TagConverter { |
||||
|
public static readonly INSTANCE: TagConverter = new StringConverter(); |
||||
|
|
||||
|
private constructor() {} |
||||
|
|
||||
|
public convertInput(input: string) { |
||||
|
if (input) { |
||||
|
const trimmed = input.trim(); |
||||
|
|
||||
|
if (trimmed.length > 0) { |
||||
|
return new TagValue(trimmed, trimmed, trimmed); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public convertValue(value: any) { |
||||
|
if (Types.isString(value)) { |
||||
|
const trimmed = value.trim(); |
||||
|
|
||||
|
return new TagValue(trimmed, trimmed, trimmed); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function getTagValues(values: ReadonlyArray<string | TagValue>) { |
||||
|
|
||||
|
if (!Types.isArray(values)) { |
||||
|
return []; |
||||
|
} |
||||
|
const result: TagValue[] = []; |
||||
|
|
||||
|
for (let value of values) { |
||||
|
if (Types.isString(value)) { |
||||
|
result.push(new TagValue(value, value, value)); |
||||
|
} else { |
||||
|
result.push(value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result.sortedByString(x => x.lowerCaseName); |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<sqx-checkbox-group [formControl]="selectionControl" |
||||
|
[values]="snapshot.converter.suggestions"> |
||||
|
</sqx-checkbox-group> |
||||
@ -0,0 +1,124 @@ |
|||||
|
/* |
||||
|
* 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, LanguageDto, StatefulControlComponent, UIOptions } from '@app/shared/internal'; |
||||
|
import { ReferencesTagsConverter } from './references-tag-converter'; |
||||
|
|
||||
|
export const SQX_REFERENCES_CHECKBOXES_CONTROL_VALUE_ACCESSOR: any = { |
||||
|
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReferencesCheckboxesComponent), multi: true |
||||
|
}; |
||||
|
|
||||
|
interface State { |
||||
|
// The tags converter.
|
||||
|
converter: ReferencesTagsConverter; |
||||
|
} |
||||
|
|
||||
|
const NO_EMIT = { emitEvent: false }; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-references-checkboxes', |
||||
|
styleUrls: ['./references-checkboxes.component.scss'], |
||||
|
templateUrl: './references-checkboxes.component.html', |
||||
|
providers: [ |
||||
|
SQX_REFERENCES_CHECKBOXES_CONTROL_VALUE_ACCESSOR |
||||
|
], |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class ReferencesCheckboxesComponent 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 ReferencesTagsConverter(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, { take: this.itemCount }) |
||||
|
.subscribe(contents => { |
||||
|
this.contentItems = contents.items; |
||||
|
|
||||
|
this.resetConverterState(); |
||||
|
}, () => { |
||||
|
this.contentItems = null; |
||||
|
|
||||
|
this.resetConverterState(); |
||||
|
}); |
||||
|
} else { |
||||
|
this.contentItems = null; |
||||
|
|
||||
|
this.resetConverterState(); |
||||
|
} |
||||
|
} else { |
||||
|
this.resetConverterState(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public setDisabledState(isDisabled: boolean) { |
||||
|
if (isDisabled) { |
||||
|
this.selectionControl.disable(NO_EMIT); |
||||
|
} else if (this.isValid) { |
||||
|
this.selectionControl.enable(NO_EMIT); |
||||
|
} |
||||
|
|
||||
|
super.setDisabledState(isDisabled); |
||||
|
} |
||||
|
|
||||
|
public writeValue(obj: ReadonlyArray<string>) { |
||||
|
this.selectionControl.setValue(obj, NO_EMIT); |
||||
|
} |
||||
|
|
||||
|
private resetConverterState() { |
||||
|
let converter: ReferencesTagsConverter; |
||||
|
|
||||
|
if (this.isValid && this.contentItems && this.contentItems.length > 0) { |
||||
|
converter = new ReferencesTagsConverter(this.language, this.contentItems); |
||||
|
|
||||
|
this.selectionControl.enable(NO_EMIT); |
||||
|
} else { |
||||
|
converter = new ReferencesTagsConverter(null!, []); |
||||
|
|
||||
|
this.selectionControl.disable(NO_EMIT); |
||||
|
} |
||||
|
|
||||
|
this.next({ converter }); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ContentDto, getContentValue, LanguageDto, TagConverter, TagValue } from '@app/shared/internal'; |
||||
|
|
||||
|
export class ReferencesTagsConverter implements TagConverter { |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
@ -1,3 +1,3 @@ |
|||||
<sqx-tag-editor placeholder=", to add reference" [converter]="snapshot.converter" [formControl]="selectionControl" |
<sqx-tag-editor placeholder=", to add reference" [converter]="snapshot.converter" [formControl]="selectionControl" |
||||
[suggestedValues]="snapshot.converter.suggestions"> |
[suggestions]="snapshot.converter.suggestions"> |
||||
</sqx-tag-editor> |
</sqx-tag-editor> |
||||
Loading…
Reference in new issue