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
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.ComponentModel.DataAnnotations; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Converters; |
|||
using Squidex.Core.Apps; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace Squidex.Controllers.Api.Apps.Models |
|||
{ |
|||
public sealed class AssignContributorDto |
|||
public sealed class AssignAppContributorDto |
|||
{ |
|||
/// <summary>
|
|||
/// The id of the user to add to the app (GUID).
|
|||
@ -1,14 +1,13 @@ |
|||
// ==========================================================================
|
|||
// SetMasterLanguageDto.cs
|
|||
// UpdateAppLanguageDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Controllers.Api.Apps.Models |
|||
{ |
|||
public class SetMasterLanguageDto |
|||
public class UpdateAppLanguageDto |
|||
{ |
|||
/// <summary>
|
|||
/// 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