mirror of https://github.com/Squidex/squidex.git
57 changed files with 873 additions and 361 deletions
@ -1,19 +1,19 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// AssignContributorDto.cs
|
// AssignAppContributorDto.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
|
using System.ComponentModel.DataAnnotations; |
||||
using Newtonsoft.Json; |
using Newtonsoft.Json; |
||||
using Newtonsoft.Json.Converters; |
using Newtonsoft.Json.Converters; |
||||
using Squidex.Core.Apps; |
using Squidex.Core.Apps; |
||||
using System.ComponentModel.DataAnnotations; |
|
||||
|
|
||||
namespace Squidex.Controllers.Api.Apps.Models |
namespace Squidex.Controllers.Api.Apps.Models |
||||
{ |
{ |
||||
public sealed class AssignContributorDto |
public sealed class AssignAppContributorDto |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// The id of the user to add to the app (GUID).
|
/// The id of the user to add to the app (GUID).
|
||||
@ -1,14 +1,13 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// SetMasterLanguageDto.cs
|
// UpdateAppLanguageDto.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
namespace Squidex.Controllers.Api.Apps.Models |
namespace Squidex.Controllers.Api.Apps.Models |
||||
{ |
{ |
||||
public class SetMasterLanguageDto |
public class UpdateAppLanguageDto |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Set the value to true to make the language to the master language.
|
/// Set the value to true to make the language to the master language.
|
||||
@ -0,0 +1,45 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import * as Ng2 from '@angular/core'; |
||||
|
|
||||
|
@Ng2.Directive({ |
||||
|
selector: '[sqxCopy]' |
||||
|
}) |
||||
|
export class CopyDirective { |
||||
|
@Ng2.Input('sqxCopy') |
||||
|
public inputElement: Ng2.ElementRef; |
||||
|
|
||||
|
@Ng2.HostListener('click') |
||||
|
public onClick() { |
||||
|
if (this.inputElement) { |
||||
|
this.copyToClipbord(this.inputElement.nativeElement); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private copyToClipbord(element: HTMLInputElement | HTMLTextAreaElement) { |
||||
|
const currentFocus: any = document.activeElement; |
||||
|
|
||||
|
const prevSelectionStart = element.selectionStart; |
||||
|
const prevSelectionEnd = element.selectionEnd; |
||||
|
|
||||
|
element.focus(); |
||||
|
element.setSelectionRange(0, element.value.length); |
||||
|
|
||||
|
try { |
||||
|
document.execCommand('copy'); |
||||
|
} catch (e) { |
||||
|
console.log('Copy failed'); |
||||
|
} |
||||
|
|
||||
|
if (currentFocus && typeof currentFocus.focus === 'function') { |
||||
|
currentFocus.focus(); |
||||
|
} |
||||
|
|
||||
|
element.setSelectionRange(prevSelectionStart, prevSelectionEnd); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import * as Ng2 from '@angular/core'; |
||||
|
|
||||
|
import { TitleService } from './../services/title.service'; |
||||
|
|
||||
|
@Ng2.Component({ |
||||
|
selector: 'sqx-title', |
||||
|
template: '' |
||||
|
}) |
||||
|
export class TitleComponent implements Ng2.OnChanges { |
||||
|
@Ng2.Input() |
||||
|
public message: any; |
||||
|
|
||||
|
@Ng2.Input() |
||||
|
public parameter: string; |
||||
|
|
||||
|
@Ng2.Input() |
||||
|
public value: any; |
||||
|
|
||||
|
constructor( |
||||
|
private readonly titleService: TitleService |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public ngOnChanges() { |
||||
|
const parameters = {}; |
||||
|
|
||||
|
if (this.parameter) { |
||||
|
if (!this.value) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
parameters[this.parameter] = this.value; |
||||
|
} |
||||
|
|
||||
|
this.titleService.setTitle(this.message, parameters); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import { ArrayHelper } from './../'; |
||||
|
|
||||
|
describe('ArrayHelper', () => { |
||||
|
it('should push item', () => { |
||||
|
const oldArray = [1, 2, 3]; |
||||
|
const newArray = ArrayHelper.push(oldArray, 4); |
||||
|
|
||||
|
expect(newArray).toEqual([1, 2, 3, 4]); |
||||
|
}); |
||||
|
|
||||
|
it('should remove item if found', () => { |
||||
|
const oldArray = [1, 2, 3]; |
||||
|
const newArray = ArrayHelper.remove(oldArray, 2); |
||||
|
|
||||
|
expect(newArray).toEqual([1, 3]); |
||||
|
}); |
||||
|
|
||||
|
it('should not remove item if not found', () => { |
||||
|
const oldArray = [1, 2, 3]; |
||||
|
const newArray = ArrayHelper.remove(oldArray, 5); |
||||
|
|
||||
|
expect(newArray).toEqual([1, 2, 3]); |
||||
|
}); |
||||
|
|
||||
|
it('should remove all by predicate', () => { |
||||
|
const oldArray: number[] = [1, 2, 3, 4]; |
||||
|
const newArray = ArrayHelper.removeAll(oldArray, (i: number) => i % 2 === 0); |
||||
|
|
||||
|
expect(newArray).toEqual([1, 3]); |
||||
|
}); |
||||
|
|
||||
|
it('should return original if nothing has been removed', () => { |
||||
|
const oldArray: number[] = [1, 2, 3, 4]; |
||||
|
const newArray = ArrayHelper.removeAll(oldArray, (i: number) => i % 200 === 0); |
||||
|
|
||||
|
expect(newArray).toEqual(oldArray); |
||||
|
}); |
||||
|
|
||||
|
it('should replace item if found', () => { |
||||
|
const oldArray = [1, 2, 3]; |
||||
|
const newArray = ArrayHelper.replace(oldArray, 2, 4); |
||||
|
|
||||
|
expect(newArray).toEqual([1, 4, 3]); |
||||
|
}); |
||||
|
|
||||
|
it('should not replace item if not found', () => { |
||||
|
const oldArray = [1, 2, 3]; |
||||
|
const newArray = ArrayHelper.replace(oldArray, 5, 5); |
||||
|
|
||||
|
expect(newArray).toEqual([1, 2, 3]); |
||||
|
}); |
||||
|
|
||||
|
it('should replace all by predicate', () => { |
||||
|
const oldArray: number[] = [1, 2, 3, 4]; |
||||
|
const newArray = ArrayHelper.replaceAll(oldArray, (i: number) => i % 2 === 0, i => i * 2); |
||||
|
|
||||
|
expect(newArray).toEqual([1, 4, 3, 8]); |
||||
|
}); |
||||
|
|
||||
|
it('should return original if nothing has been replace', () => { |
||||
|
const oldArray: number[] = [1, 2, 3, 4]; |
||||
|
const newArray = ArrayHelper.replaceAll(oldArray, (i: number) => i % 200 === 0, i => i); |
||||
|
|
||||
|
expect(newArray).toEqual(oldArray); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
@ -0,0 +1,74 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
export module ArrayHelper { |
||||
|
export function push<T>(array: T[], item: T): T[] { |
||||
|
return [...array, item]; |
||||
|
} |
||||
|
|
||||
|
export function remove<T>(array: T[], item: T): T[] { |
||||
|
const index = array.indexOf(item); |
||||
|
|
||||
|
if (index >= 0) { |
||||
|
return [...array.slice(0, index), ...array.slice(index + 1)]; |
||||
|
} else { |
||||
|
return array; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function removeAll<T>(array: T[], predicate: (item: T, index: number) => boolean): T[] { |
||||
|
const copy = array.slice(); |
||||
|
|
||||
|
let hasChange = false; |
||||
|
|
||||
|
for (let i = 0; i < copy.length; ) { |
||||
|
if (predicate(copy[i], i)) { |
||||
|
copy.splice(i, 1); |
||||
|
|
||||
|
hasChange = true; |
||||
|
} else { |
||||
|
++i; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return hasChange ? copy : array; |
||||
|
} |
||||
|
|
||||
|
export function replace<T>(array: T[], oldItem: T, newItem: T): T[] { |
||||
|
const index = array.indexOf(oldItem); |
||||
|
|
||||
|
if (index >= 0) { |
||||
|
const copy = array.slice(); |
||||
|
|
||||
|
copy[index] = newItem; |
||||
|
|
||||
|
return copy; |
||||
|
} else { |
||||
|
return array; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function replaceAll<T>(array: T[], predicate: (item: T, index: number) => boolean, replacer: (item: T) => T): T[] { |
||||
|
const copy = array.slice(); |
||||
|
|
||||
|
let hasChange = false; |
||||
|
|
||||
|
for (let i = 0; i < copy.length; i++) { |
||||
|
if (predicate(copy[i], i)) { |
||||
|
const newItem = replacer(copy[i]); |
||||
|
|
||||
|
if (copy[i] !== newItem) { |
||||
|
copy[i] = newItem; |
||||
|
|
||||
|
hasChange = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return hasChange ? copy : array; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import { Observable } from 'rxjs'; |
||||
|
|
||||
|
import { |
||||
|
AppsStoreService, |
||||
|
ErrorDto, |
||||
|
Notification, |
||||
|
NotificationService, |
||||
|
UsersProviderService |
||||
|
} from 'shared'; |
||||
|
|
||||
|
export abstract class AppComponentBase { |
||||
|
constructor( |
||||
|
private readonly appsStore: AppsStoreService, |
||||
|
private readonly notifications: NotificationService, |
||||
|
private readonly usersProvider: UsersProviderService |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public appName(): Observable<string> { |
||||
|
return this.appsStore.selectedApp.map(a => a.name); |
||||
|
} |
||||
|
|
||||
|
public userEmail(userId: string): Observable<string> { |
||||
|
return this.usersProvider.getUser(userId).map(u => u.email); |
||||
|
} |
||||
|
|
||||
|
public userName(userId: string): Observable<string> { |
||||
|
return this.usersProvider.getUser(userId).map(u => u.displayName); |
||||
|
} |
||||
|
|
||||
|
public userPicture(userId: string): Observable<string> { |
||||
|
return this.usersProvider.getUser(userId).map(u => u.pictureUrl); |
||||
|
} |
||||
|
|
||||
|
public notifyError(error: string | ErrorDto) { |
||||
|
if (error instanceof ErrorDto) { |
||||
|
this.notifications.notify(Notification.error(error.displayMessage)); |
||||
|
} else { |
||||
|
this.notifications.notify(Notification.error(error)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public notifyInfo(error: string) { |
||||
|
this.notifications.notify(Notification.error(error)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,187 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import * as Ng2 from '@angular/core'; |
||||
|
|
||||
|
import { Observable } from 'rxjs'; |
||||
|
|
||||
|
import { ApiUrlConfig, DateTime } from 'framework'; |
||||
|
import { AuthService } from './auth.service'; |
||||
|
|
||||
|
import { handleError } from './common'; |
||||
|
|
||||
|
export class SchemaDto { |
||||
|
constructor( |
||||
|
public readonly id: string, |
||||
|
public readonly name: string, |
||||
|
public readonly created: DateTime, |
||||
|
public readonly lastModified: DateTime |
||||
|
) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class SchemaDetailsDto { |
||||
|
constructor( |
||||
|
public readonly id: string, |
||||
|
public readonly name: string, |
||||
|
public readonly created: DateTime, |
||||
|
public readonly lastModified: DateTime, |
||||
|
public readonly fields: FieldDto[] |
||||
|
) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class FieldDto { |
||||
|
constructor( |
||||
|
public readonly name: string, |
||||
|
public readonly isHidden: boolean, |
||||
|
public readonly isDisabled: boolean, |
||||
|
public readonly properties: FieldPropertiesDto |
||||
|
) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export abstract class FieldPropertiesDto { |
||||
|
constructor( |
||||
|
public readonly label: string, |
||||
|
public readonly hints: string, |
||||
|
public readonly placeholder: string, |
||||
|
public readonly isRequired: boolean, |
||||
|
) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class NumberFieldPropertiesDto extends FieldPropertiesDto { |
||||
|
constructor(label: string, hints: string, placeholder: string, isRequired: boolean, |
||||
|
public readonly defaultValue: number | null, |
||||
|
public readonly maxValue: number | null, |
||||
|
public readonly minValue: number | null, |
||||
|
public readonly allowedValues: number[] |
||||
|
) { |
||||
|
super(label, hints, placeholder, isRequired); |
||||
|
|
||||
|
this['$type'] = 'Number'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class StringFieldPropertiesDto extends FieldPropertiesDto { |
||||
|
constructor(label: string, hints: string, placeholder: string, isRequired: boolean, |
||||
|
public readonly defaultValue: string, |
||||
|
public readonly pattern: string, |
||||
|
public readonly patternMessage: string, |
||||
|
public readonly minLength: number | null, |
||||
|
public readonly maxLength: number | null, |
||||
|
public readonly allowedValues: number[] |
||||
|
) { |
||||
|
super(label, hints, placeholder, isRequired); |
||||
|
|
||||
|
this['$type'] = 'String'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class UpdateSchemaDto { |
||||
|
constructor( |
||||
|
public readonly label: string, |
||||
|
public readonly hints: string |
||||
|
) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class UpdateFieldDto { |
||||
|
constructor( |
||||
|
public readonly properties: FieldPropertiesDto |
||||
|
) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class CreateSchemaDto { |
||||
|
constructor( |
||||
|
public readonly name: string |
||||
|
) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Ng2.Injectable() |
||||
|
export class SchemasService { |
||||
|
constructor( |
||||
|
private readonly authService: AuthService, |
||||
|
private readonly apiUrl: ApiUrlConfig |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public getSchemas(appName: string): Observable<SchemaDto[]> { |
||||
|
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`); |
||||
|
|
||||
|
return this.authService.authGet(url) |
||||
|
.map(response => response.json()) |
||||
|
.map(response => { |
||||
|
const items: any[] = response; |
||||
|
|
||||
|
return items.map(item => { |
||||
|
return new SchemaDto( |
||||
|
item.id, |
||||
|
item.name, |
||||
|
DateTime.parseISO_UTC(item.created), |
||||
|
DateTime.parseISO_UTC(item.lastModified)); |
||||
|
}); |
||||
|
}) |
||||
|
.catch(response => handleError('Failed to load schemas. Please reload.', response)); |
||||
|
} |
||||
|
|
||||
|
public getSchema(appName: string, id: string): Observable<SchemaDetailsDto> { |
||||
|
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/{id}`); |
||||
|
|
||||
|
return this.authService.authGet(url) |
||||
|
.map(response => response.json()) |
||||
|
.map(response => { |
||||
|
const fields = response.fields.map((item: any) => { |
||||
|
const typeName = item['$type']; |
||||
|
|
||||
|
let properties = item.properties; |
||||
|
let propertiesDto: FieldPropertiesDto; |
||||
|
|
||||
|
if (typeName === 'String') { |
||||
|
propertiesDto = new StringFieldPropertiesDto( |
||||
|
properties.label, |
||||
|
properties.hints, |
||||
|
properties.placeholder, |
||||
|
properties.isRequired, |
||||
|
properties.defaultValue, |
||||
|
properties.pattern, |
||||
|
properties.patternMessage, |
||||
|
properties.minLength, |
||||
|
properties.maxLength, |
||||
|
properties.allowedValues); |
||||
|
} else { |
||||
|
propertiesDto = new NumberFieldPropertiesDto( |
||||
|
properties.label, |
||||
|
properties.hints, |
||||
|
properties.placeholder, |
||||
|
properties.isRequired, |
||||
|
properties.defaultValue, |
||||
|
properties.minValue, |
||||
|
properties.maxValue, |
||||
|
properties.allowedValues); |
||||
|
} |
||||
|
|
||||
|
return new FieldDto( |
||||
|
item.name, |
||||
|
item.isHidden, |
||||
|
item.isDisabled, |
||||
|
propertiesDto); |
||||
|
}); |
||||
|
|
||||
|
return new SchemaDetailsDto( |
||||
|
response.id, |
||||
|
response.name, |
||||
|
DateTime.parseISO_UTC(response.created), |
||||
|
DateTime.parseISO_UTC(response.lastModified), |
||||
|
fields); |
||||
|
}) |
||||
|
.catch(response => handleError('Failed to load schema. Please reload.', response)); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue