Browse Source

First comments version finalized.

pull/329/head
Sebastian Stehle 7 years ago
parent
commit
b73bc21145
  1. 1
      src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs
  2. 2
      src/Squidex.Domain.Apps.Events/Comments/CommentDeleted.cs
  3. 32
      src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs
  4. 2
      src/Squidex/Areas/Api/Controllers/Comments/Models/CommentDto.cs
  5. 1
      src/Squidex/app/features/content/declarations.ts
  6. 8
      src/Squidex/app/features/content/module.ts
  7. 1
      src/Squidex/app/features/content/pages/comments/comments-page.component.html
  8. 2
      src/Squidex/app/features/content/pages/comments/comments-page.component.scss
  9. 30
      src/Squidex/app/features/content/pages/comments/comments-page.component.ts
  10. 2
      src/Squidex/app/features/content/pages/content/content-history.component.html
  11. 4
      src/Squidex/app/features/content/pages/content/content-page.component.html
  12. 19
      src/Squidex/app/shared/components/comment.component.html
  13. 43
      src/Squidex/app/shared/components/comment.component.scss
  14. 38
      src/Squidex/app/shared/components/comment.component.ts
  15. 26
      src/Squidex/app/shared/components/comments.component.html
  16. 11
      src/Squidex/app/shared/components/comments.component.scss
  17. 84
      src/Squidex/app/shared/components/comments.component.ts
  18. 2
      src/Squidex/app/shared/components/history.component.html
  19. 2
      src/Squidex/app/shared/declarations.ts
  20. 1
      src/Squidex/app/shared/internal.ts
  21. 6
      src/Squidex/app/shared/module.ts
  22. 18
      src/Squidex/app/shared/services/comments.service.spec.ts
  23. 40
      src/Squidex/app/shared/services/comments.service.ts
  24. 22
      src/Squidex/app/shared/state/comments.form.ts
  25. 119
      src/Squidex/app/shared/state/comments.state.spec.ts
  26. 33
      src/Squidex/app/shared/state/comments.state.ts
  27. 8
      src/Squidex/app/theme/icomoon/demo-files/demo.css
  28. 324
      src/Squidex/app/theme/icomoon/demo.html
  29. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.eot
  30. 1
      src/Squidex/app/theme/icomoon/fonts/icomoon.svg
  31. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.ttf
  32. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.woff
  33. 2
      src/Squidex/app/theme/icomoon/selection.json
  34. 40
      src/Squidex/app/theme/icomoon/style.css

1
src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs

@ -65,7 +65,6 @@ namespace Squidex.Domain.Apps.Entities.Comments
if (events.Length > 0)
{
await persistence.WriteEventsAsync(events);
await persistence.WriteSnapshotAsync(Snapshot);
}
}

2
src/Squidex.Domain.Apps.Events/Comments/CommentDeleted.cs

@ -10,7 +10,7 @@ using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Comments
{
[EventType(nameof(CommentCreated))]
[EventType(nameof(CommentDeleted))]
public sealed class CommentDeleted : CommentsEvent
{
public Guid CommentId { get; set; }

32
src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs

@ -21,8 +21,10 @@ namespace Squidex.Areas.Api.Controllers.Comments
/// <summary>
/// Manages comments for any kind of resource.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[ApiExplorerSettings(GroupName = nameof(Languages))]
[AppApi]
[ApiExplorerSettings(GroupName = nameof(Comments))]
public sealed class CommentsController : ApiController
{
private readonly IGrainFactory grainFactory;
@ -36,17 +38,19 @@ namespace Squidex.Areas.Api.Controllers.Comments
/// <summary>
/// Get all comments.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="commentsId">The id of the comments.</param>
/// <returns>
/// 200 => All comments returned.
/// 404 => App not found.
/// </returns>
[HttpGet]
[Route("comments/{commentsId}")]
[Route("apps/{app}/comments/{commentsId}")]
[ProducesResponseType(typeof(CommentsDto), 200)]
[ApiCosts(0)]
public async Task<IActionResult> GetComments(Guid commentsId)
public async Task<IActionResult> GetComments(string app, Guid commentsId)
{
if (!long.TryParse(Request.Headers["X-Since"], out var version))
if (!long.TryParse(Request.Headers["If-None-Match"], out var version))
{
version = EtagVersion.Any;
}
@ -62,18 +66,20 @@ namespace Squidex.Areas.Api.Controllers.Comments
/// <summary>
/// Create a new comment.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="commentsId">The id of the comments.</param>
/// <param name="request">The comment object that needs to created.</param>
/// <returns>
/// 201 => Comment created.
/// 400 => Comment is not valid.
/// 404 => App not found.
/// </returns>
[HttpPost]
[Route("comments/{commentdsId}")]
[Route("apps/{app}/comments/{commentsId}")]
[ProducesResponseType(typeof(EntityCreatedDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiCosts(0)]
public async Task<IActionResult> PostComment(Guid commentsId, [FromBody] UpsertCommentDto request)
public async Task<IActionResult> PostComment(string app, Guid commentsId, [FromBody] UpsertCommentDto request)
{
var command = request.ToCreateCommand(commentsId);
var context = await CommandBus.PublishAsync(command);
@ -86,20 +92,21 @@ namespace Squidex.Areas.Api.Controllers.Comments
/// <summary>
/// Updates the comment.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="commentsId">The id of the comments.</param>
/// <param name="commentId">The id of the comment.</param>
/// <param name="request">The comment object that needs to updated.</param>
/// <returns>
/// 204 => Comment updated.
/// 400 => Comment text not valid.
/// 404 => Comment not found.
/// 404 => Comment or app not found.
/// </returns>
[MustBeAppReader]
[HttpPut]
[Route("comments/{commentdsId}/{commentId}")]
[Route("apps/{app}/comments/{commentsId}/{commentId}")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiCosts(0)]
public async Task<IActionResult> PutComment(Guid commentsId, Guid commentId, [FromBody] UpsertCommentDto request)
public async Task<IActionResult> PutComment(string app, Guid commentsId, Guid commentId, [FromBody] UpsertCommentDto request)
{
await CommandBus.PublishAsync(request.ToUpdateComment(commentsId, commentId));
@ -109,17 +116,18 @@ namespace Squidex.Areas.Api.Controllers.Comments
/// <summary>
/// Deletes the comment.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="commentsId">The id of the comments.</param>
/// <param name="commentId">The id of the comment.</param>
/// <returns>
/// 204 => Comment deleted.
/// 404 => Comment not found.
/// 404 => Comment or app not found.
/// </returns>
[HttpDelete]
[Route("comments/{commentdsId}/{commentId}")]
[Route("apps/{app}/comments/{commentsId}/{commentId}")]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiCosts(0)]
public async Task<IActionResult> DeleteComment(Guid commentsId, Guid commentId)
public async Task<IActionResult> DeleteComment(string app, Guid commentsId, Guid commentId)
{
await CommandBus.PublishAsync(new DeleteComment { CommentsId = commentsId, CommentId = commentId });

2
src/Squidex/Areas/Api/Controllers/Comments/Models/CommentDto.cs

@ -47,7 +47,7 @@ namespace Squidex.Areas.Api.Controllers.Comments.Models
public static CommentDto FromCommand(CreateComment command)
{
return SimpleMapper.Map(command, new CommentDto { User = command.Actor, Time = SystemClock.Instance.GetCurrentInstant() });
return SimpleMapper.Map(command, new CommentDto { Id = command.CommentId, User = command.Actor, Time = SystemClock.Instance.GetCurrentInstant() });
}
}
}

1
src/Squidex/app/features/content/declarations.ts

@ -5,6 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './pages/comments/comments-page.component';
export * from './pages/content/content-field.component';
export * from './pages/content/content-history.component';
export * from './pages/content/content-page.component';

8
src/Squidex/app/features/content/module.ts

@ -25,6 +25,7 @@ import {
ArrayEditorComponent,
ArrayItemComponent,
AssetsEditorComponent,
CommentsPageComponent,
ContentFieldComponent,
ContentHistoryComponent,
ContentItemComponent,
@ -75,7 +76,11 @@ const routes: Routes = [
data: {
channel: 'contents.{contentId}'
}
}
},
{
path: 'comments',
component: CommentsPageComponent
}
]
}
]
@ -95,6 +100,7 @@ const routes: Routes = [
ArrayEditorComponent,
ArrayItemComponent,
AssetsEditorComponent,
CommentsPageComponent,
ContentFieldComponent,
ContentHistoryComponent,
ContentItemComponent,

1
src/Squidex/app/features/content/pages/comments/comments-page.component.html

@ -0,0 +1 @@
<sqx-comments [commentsId]="commentsId"></sqx-comments>

2
src/Squidex/app/features/content/pages/comments/comments-page.component.scss

@ -0,0 +1,2 @@
@import '_vars';
@import '_mixins';

30
src/Squidex/app/features/content/pages/comments/comments-page.component.ts

@ -0,0 +1,30 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { allParams } from '@app/shared';
@Component({
selector: 'sqx-comments-page',
styleUrls: ['./comments-page.component.scss'],
templateUrl: './comments-page.component.html'
})
export class CommentsPageComponent implements OnInit {
public commentsId: string;
constructor(
private readonly route: ActivatedRoute
) {
}
public ngOnInit() {
this.commentsId = allParams(this.route)['contentId'];
}
}

2
src/Squidex/app/features/content/pages/content/content-history.component.html

@ -1,4 +1,4 @@
<sqx-panel desiredWidth="16rem" isBlank="true" [isLazyLoaded]="false">
<sqx-panel desiredWidth="20rem" isBlank="true" [isLazyLoaded]="false">
<ng-container title>
Activity
</ng-container>

4
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -125,6 +125,10 @@
<i class="icon-time"></i>
</a>
<a class="panel-link" routerLink="comments" routerLinkActive="active" #linkHistory>
<i class="icon-comments"></i>
</a>
<sqx-onboarding-tooltip helpId="history" [for]="linkHistory" position="leftTop" after="120000">
The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.
</sqx-onboarding-tooltip>

19
src/Squidex/app/shared/components/comment.component.html

@ -0,0 +1,19 @@
<div class="comment row no-gutters">
<div class="col col-auto">
<img class="user-picture" [attr.title]="comment.user | sqxUserNameRef:null" [attr.src]="comment.user | sqxUserPictureRef" />
</div>
<div class="col pl-2">
<div class="comment-message">
<div class="user-row">
<div class="user-ref">{{comment.user | sqxUserNameRef:null}}</div>
<button *ngIf="comment.user === userId" type="button" class="btn btn-sm btn-link btn-danger item-remove" (click)="deleting.emit()!">
<i class="icon-bin2"></i>
</button>
</div>
<div>{{comment.text}}</div>
<div class="comment-created text-muted">{{comment.time | sqxFromNow}}</div>
</div>
</div>
</div>

43
src/Squidex/app/shared/components/comment.component.scss

@ -0,0 +1,43 @@
@import '_vars';
@import '_mixins';
.user-ref {
font-weight: bold;
}
.item-remove {
@include absolute(-5px, -15px, auto, auto);
display: none;
}
.user-row {
& {
position: relative;
}
&:hover {
.item-remove {
display: block;
}
}
}
.comment {
& {
font-size: .9rem;
font-weight: normal;
margin-bottom: .75rem;
}
&-message {
margin-bottom: .375rem;
}
&-created {
font-size: .75rem;
}
}
.text-muted {
color: $color-history;
}

38
src/Squidex/app/shared/components/comment.component.ts

@ -0,0 +1,38 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { CommentDto, UpsertCommentForm } from '@app/shared/internal';
@Component({
selector: 'sqx-comment',
styleUrls: ['./comment.component.scss'],
templateUrl: './comment.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CommentComponent {
public editForm = new UpsertCommentForm(this.formBuilder);
@Input()
public comment: CommentDto;
@Input()
public userId: string;
@Output()
public deleting = new EventEmitter();
@Output()
public updated = new EventEmitter<string>();
constructor(
private readonly formBuilder: FormBuilder
) {
}
}

26
src/Squidex/app/shared/components/comments.component.html

@ -0,0 +1,26 @@
<sqx-panel desiredWidth="20rem" isBlank="true" [isLazyLoaded]="false" contentClass="grid">
<ng-container title>
Comments
</ng-container>
<ng-container content>
<div class="grid-content" #scrollMe [scrollTop]="scrollMe.scrollHeight">
<sqx-comment *ngFor="let comment of state.comments | async; trackBy: trackByComment"
[comment]="comment"
[userId]="userId"
(updated)="update(comment, $event)"
(deleting)="delete(comment)">
</sqx-comment>
</div>
<div class="grid-footer">
<form [formGroup]="commentForm.form" (submit)="comment()">
<input class="form-control" name="text" formControlName="text" placeholder="Create a comment" />
</form>
</div>
</ng-container>
</sqx-panel>

11
src/Squidex/app/shared/components/comments.component.scss

@ -0,0 +1,11 @@
@import '_vars';
@import '_mixins';
.grid-footer {
border-top-width: 1px;
}
.grid-body,
.grid-footer {
padding: 1rem;
}

84
src/Squidex/app/shared/components/comments.component.ts

@ -0,0 +1,84 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Subscription, timer } from 'rxjs';
import { onErrorResumeNext, switchMap } from 'rxjs/operators';
import {
AppsState,
AuthService,
CommentDto,
CommentsService,
CommentsState,
DialogService,
fadeAnimation,
UpsertCommentForm
} from '@app/shared/internal';
@Component({
selector: 'sqx-comments',
styleUrls: ['./comments.component.scss'],
templateUrl: './comments.component.html',
animations: [
fadeAnimation
]
})
export class CommentsComponent implements OnDestroy, OnInit {
private timer: Subscription;
public state: CommentsState;
public userId: string;
public commentForm = new UpsertCommentForm(this.formBuilder);
@Input()
public commentsId: string;
constructor(authService: AuthService,
private readonly appsState: AppsState,
private readonly commentsService: CommentsService,
private readonly dialogs: DialogService,
private readonly formBuilder: FormBuilder
) {
this.userId = authService.user!.token;
}
public ngOnDestroy() {
this.timer.unsubscribe();
}
public ngOnInit() {
this.state = new CommentsState(this.appsState, this.commentsId, this.commentsService, this.dialogs);
this.timer = timer(0, 4000).pipe(switchMap(() => this.state.load()), onErrorResumeNext()).subscribe();
}
public delete(comment: CommentDto) {
this.state.delete(comment.id).pipe(onErrorResumeNext()).subscribe();
}
public update(comment: CommentDto, text: string) {
this.state.update(comment.id, text).pipe(onErrorResumeNext()).subscribe();
}
public comment() {
const value = this.commentForm.submit();
if (value) {
this.state.create(value.text).pipe(onErrorResumeNext()).subscribe();
this.commentForm.submitCompleted({});
}
}
public trackByComment(index: number, comment: CommentDto) {
return comment.id;
}
}

2
src/Squidex/app/shared/components/history.component.html

@ -1,4 +1,4 @@
<sqx-panel desiredWidth="16rem" isBlank="true" [isLazyLoaded]="false">
<sqx-panel desiredWidth="20rem" isBlank="true" [isLazyLoaded]="false">
<ng-container title>
Activity
</ng-container>

2
src/Squidex/app/shared/declarations.ts

@ -9,6 +9,8 @@ export * from './components/app-form.component';
export * from './components/asset.component';
export * from './components/assets-list.component';
export * from './components/assets-selector.component';
export * from './components/comment.component';
export * from './components/comments.component';
export * from './components/help.component';
export * from './components/geolocation-editor.component';
export * from './components/history.component';

1
src/Squidex/app/shared/internal.ts

@ -50,6 +50,7 @@ export * from './state/backups.forms';
export * from './state/backups.state';
export * from './state/clients.forms';
export * from './state/clients.state';
export * from './state/comments.form';
export * from './state/comments.state';
export * from './state/contents.forms';
export * from './state/contents.state';

6
src/Squidex/app/shared/module.ts

@ -34,6 +34,8 @@ import {
BackupsService,
BackupsState,
ClientsState,
CommentComponent,
CommentsComponent,
CommentsService,
CommentsState,
ContentMustExistGuard,
@ -99,6 +101,8 @@ import {
AssetUrlPipe,
AssetsListComponent,
AssetsSelectorComponent,
CommentComponent,
CommentsComponent,
FileIconPipe,
GeolocationEditorComponent,
HelpComponent,
@ -124,6 +128,8 @@ import {
AssetUrlPipe,
AssetsListComponent,
AssetsSelectorComponent,
CommentComponent,
CommentsComponent,
FileIconPipe,
GeolocationEditorComponent,
HelpComponent,

18
src/Squidex/app/shared/services/comments.service.spec.ts

@ -42,14 +42,14 @@ describe('CommentsService', () => {
let comments: CommentsDto;
commentsService.getComments('my-comments', new Version('123')).subscribe(result => {
commentsService.getComments('my-app', 'my-comments', new Version('123')).subscribe(result => {
comments = result;
});
const req = httpMock.expectOne('http://service/p/api/comments/my-comments');
const req = httpMock.expectOne('http://service/p/api/apps/my-app/comments/my-comments');
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('X-Since')).toBe('123');
expect(req.request.headers.get('If-None-Match')).toBe('123');
req.flush({
createdComments: [{
@ -86,11 +86,11 @@ describe('CommentsService', () => {
let comment: CommentDto;
commentsService.postComment('my-comments', new UpsertCommentDto('text1')).subscribe(result => {
commentsService.postComment('my-app', 'my-comments', new UpsertCommentDto('text1')).subscribe(result => {
comment = <CommentDto>result;
});
const req = httpMock.expectOne('http://service/p/api/comments/my-comments');
const req = httpMock.expectOne('http://service/p/api/apps/my-app/comments/my-comments');
expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toBeNull();
@ -108,9 +108,9 @@ describe('CommentsService', () => {
it('should make put request to replace comment content',
inject([CommentsService, HttpTestingController], (commentsService: CommentsService, httpMock: HttpTestingController) => {
commentsService.putComment('my-comments', '123', new UpsertCommentDto('text1')).subscribe();
commentsService.putComment('my-app', 'my-comments', '123', new UpsertCommentDto('text1')).subscribe();
const req = httpMock.expectOne('http://service/p/api/comments/my-comments/123');
const req = httpMock.expectOne('http://service/p/api/apps/my-app/comments/my-comments/123');
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBeNull();
@ -121,9 +121,9 @@ describe('CommentsService', () => {
it('should make delete request to delete comment',
inject([CommentsService, HttpTestingController], (commentsService: CommentsService, httpMock: HttpTestingController) => {
commentsService.deleteComment('my-comments', '123').subscribe();
commentsService.deleteComment('my-app', 'my-comments', '123').subscribe();
const req = httpMock.expectOne('http://service/p/api/comments/my-comments/123');
const req = httpMock.expectOne('http://service/p/api/apps/my-app/comments/my-comments/123');
expect(req.request.method).toEqual('DELETE');
expect(req.request.headers.get('If-Match')).toBeNull();

40
src/Squidex/app/shared/services/comments.service.ts

@ -5,16 +5,17 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Observable, of, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import {
ApiUrlConfig,
DateTime,
Model,
pretifyError,
Types,
Version
} from '@app/framework';
@ -59,11 +60,26 @@ export class CommentsService {
) {
}
public getComments(commentsId: string, version: Version): Observable<CommentsDto> {
const url = this.apiUrl.buildUrl(`api/comments/${commentsId}`);
public getComments(appName: string, commentsId: string, version: Version): Observable<CommentsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/comments/${commentsId}`);
return this.http.get(url, { headers: { 'X-Since': version.value } }).pipe(
const options = {
headers: new HttpHeaders().set('If-None-Match', version.value)
};
return this.http.get(url, options).pipe(
catchError(err => {
if (err.status === 304) {
return of(new CommentsDto([], [], [], version));
}
return throwError(err);
}),
map(response => {
if (Types.is(response, CommentsDto)) {
return response;
}
const body: any = response;
return new CommentsDto(
@ -88,8 +104,8 @@ export class CommentsService {
pretifyError('Failed to load comments.'));
}
public postComment(commentsId: string, dto: UpsertCommentDto): Observable<CommentDto> {
const url = this.apiUrl.buildUrl(`api/comments/${commentsId}`);
public postComment(appName: string, commentsId: string, dto: UpsertCommentDto): Observable<CommentDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/comments/${commentsId}`);
return this.http.post(url, dto).pipe(
map(response => {
@ -104,8 +120,8 @@ export class CommentsService {
pretifyError('Failed to create comment.'));
}
public putComment(commentsId: string, commentId: string, dto: UpsertCommentDto): Observable<any> {
const url = this.apiUrl.buildUrl(`api/comments/${commentsId}/${commentId}`);
public putComment(appName: string, commentsId: string, commentId: string, dto: UpsertCommentDto): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/comments/${commentsId}/${commentId}`);
return this.http.put(url, dto).pipe(
map(response => {
@ -120,8 +136,8 @@ export class CommentsService {
pretifyError('Failed to update comment.'));
}
public deleteComment(commentsId: string, commentId: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/comments/${commentsId}/${commentId}`);
public deleteComment(appName: string, commentsId: string, commentId: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/comments/${commentsId}/${commentId}`);
return this.http.delete(url).pipe(
pretifyError('Failed to delete comment.'));

22
src/Squidex/app/shared/state/comments.form.ts

@ -0,0 +1,22 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Form } from '@app/framework';
export class UpsertCommentForm extends Form<FormGroup> {
constructor(formBuilder: FormBuilder) {
super(formBuilder.group({
text: ['',
[
Validators.required
]
]
}));
}
}

119
src/Squidex/app/shared/state/comments.state.spec.ts

@ -0,0 +1,119 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { of } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq';
import {
AppsState,
CommentDto,
CommentsDto,
CommentsService,
CommentsState,
DateTime,
DialogService,
ImmutableArray,
UpsertCommentDto,
Version
} from '@app/shared';
describe('CommentsState', () => {
const app = 'my-app';
const commentsId = 'my-comments';
const now = DateTime.today();
const user = 'not-me';
const oldComments = new CommentsDto([
new CommentDto('1', now, 'text1', user),
new CommentDto('2', now, 'text2', user)
], [], [], new Version('1'));
let dialogs: IMock<DialogService>;
let appsState: IMock<AppsState>;
let commentsService: IMock<CommentsService>;
let commentsState: CommentsState;
beforeEach(() => {
dialogs = Mock.ofType<DialogService>();
appsState = Mock.ofType<AppsState>();
appsState.setup(x => x.appName)
.returns(() => app);
commentsService = Mock.ofType<CommentsService>();
commentsService.setup(x => x.getComments(app, commentsId, new Version('')))
.returns(() => of(oldComments));
commentsState = new CommentsState(appsState.object, commentsId, commentsService.object, dialogs.object);
commentsState.load().subscribe();
});
it('should load and merge comments', () => {
const newComments = new CommentsDto([
new CommentDto('3', now, 'text3', user)
], [
new CommentDto('2', now, 'text2_2', user)
], ['1'], new Version('2'));
commentsService.setup(x => x.getComments(app, commentsId, new Version('1')))
.returns(() => of(newComments));
commentsState.load().subscribe();
expect(commentsState.snapshot.isLoaded).toBeTruthy();
expect(commentsState.snapshot.comments).toEqual(ImmutableArray.of([
new CommentDto('2', now, 'text2_2', user),
new CommentDto('3', now, 'text3', user)
]));
commentsService.verify(x => x.getComments(app, commentsId, It.isAny()), Times.exactly(2));
});
it('should add comment to snapshot when created', () => {
const newComment = new CommentDto('3', now, 'text3', user);
commentsService.setup(x => x.postComment(app, commentsId, new UpsertCommentDto('text3')))
.returns(() => of(newComment));
commentsState.create('text3').subscribe();
expect(commentsState.snapshot.comments).toEqual(ImmutableArray.of([
new CommentDto('1', now, 'text1', user),
new CommentDto('2', now, 'text2', user),
new CommentDto('3', now, 'text3', user)
]));
});
it('should update properties when updated', () => {
commentsService.setup(x => x.putComment(app, commentsId, '2', new UpsertCommentDto('text2_2')))
.returns(() => of({}));
commentsState.update('2', 'text2_2', now).subscribe();
expect(commentsState.snapshot.comments).toEqual(ImmutableArray.of([
new CommentDto('1', now, 'text1', user),
new CommentDto('2', now, 'text2_2', user)
]));
commentsService.verify(x => x.putComment(app, commentsId, '2', new UpsertCommentDto('text2_2')), Times.once());
});
it('should remove comment from snapshot when deleted', () => {
commentsService.setup(x => x.deleteComment(app, commentsId, '2'))
.returns(() => of({}));
commentsState.delete('2').subscribe();
expect(commentsState.snapshot.comments).toEqual(ImmutableArray.of([
new CommentDto('1', now, 'text1', user)
]));
commentsService.verify(x => x.deleteComment(app, commentsId, '2'), Times.once());
});
});

33
src/Squidex/app/shared/state/comments.state.ts

@ -24,6 +24,8 @@ import {
UpsertCommentDto
} from './../services/comments.service';
import { AppsState } from './apps.state';
interface Snapshot {
comments: ImmutableArray<CommentDto>;
@ -43,6 +45,7 @@ export class CommentsState extends State<Snapshot> {
distinctUntilChanged());
constructor(
private readonly appsState: AppsState,
private readonly commentsId: string,
private readonly commentsService: CommentsService,
private readonly dialogs: DialogService
@ -51,13 +54,15 @@ export class CommentsState extends State<Snapshot> {
}
public load(): Observable<any> {
return this.commentsService.getComments(this.commentsId, this.version).pipe(
return this.commentsService.getComments(this.appName, this.commentsId, this.version).pipe(
tap(dtos => {
this.next(s => {
let comments = s.comments;
for (let created of dtos.createdComments) {
comments = comments.push(created);
if (!comments.find(x => x.id === created.id)) {
comments = comments.push(created);
}
}
for (let updated of dtos.updatedComments) {
@ -74,8 +79,8 @@ export class CommentsState extends State<Snapshot> {
notify(this.dialogs));
}
public create(request: UpsertCommentDto): Observable<any> {
return this.commentsService.postComment(this.commentsId, request).pipe(
public create(text: string): Observable<any> {
return this.commentsService.postComment(this.appName, this.commentsId, new UpsertCommentDto(text)).pipe(
tap(dto => {
this.next(s => {
const comments = s.comments.push(dto);
@ -86,11 +91,11 @@ export class CommentsState extends State<Snapshot> {
notify(this.dialogs));
}
public update(commentId: string, request: UpsertCommentDto, now?: DateTime): Observable<any> {
return this.commentsService.putComment(this.commentsId, commentId, request).pipe(
public update(commentId: string, text: string, now?: DateTime): Observable<any> {
return this.commentsService.putComment(this.appName, this.commentsId, commentId, new UpsertCommentDto(text)).pipe(
tap(() => {
this.next(s => {
const comments = s.comments.map(c => c.id === commentId ? update(c, request, now || DateTime.now()) : c);
const comments = s.comments.map(c => c.id === commentId ? update(c, text, now || DateTime.now()) : c);
return { ...s, comments };
});
@ -99,12 +104,12 @@ export class CommentsState extends State<Snapshot> {
}
public delete(commentId: string): Observable<any> {
return this.commentsService.deleteComment(this.commentsId, commentId).pipe(
tap(dto => {
return this.commentsService.deleteComment(this.appName, this.commentsId, commentId).pipe(
tap(() => {
this.next(s => {
const comments = s.comments.filter(c => c.id !== commentId);
return { ...s, comments, version: dto.version };
return { ...s, comments };
});
}),
notify(this.dialogs));
@ -113,7 +118,11 @@ export class CommentsState extends State<Snapshot> {
private get version() {
return this.snapshot.version;
}
private get appName() {
return this.appsState.appName;
}
}
const update = (comment: CommentDto, request: UpsertCommentDto, now: DateTime) =>
comment.with({ text: request.text, time: now });
const update = (comment: CommentDto, text: string, time: DateTime) =>
comment.with({ text, time });

8
src/Squidex/app/theme/icomoon/demo-files/demo.css

@ -147,19 +147,19 @@ p {
font-size: 16px;
}
.fs1 {
font-size: 32px;
font-size: 24px;
}
.fs2 {
font-size: 20px;
font-size: 32px;
}
.fs3 {
font-size: 32px;
font-size: 20px;
}
.fs4 {
font-size: 32px;
}
.fs5 {
font-size: 24px;
font-size: 32px;
}
.fs6 {
font-size: 28px;

324
src/Squidex/app/theme/icomoon/demo.html

@ -9,11 +9,94 @@
<link rel="stylesheet" href="style.css"></head>
<body>
<div class="bgc1 clearfix">
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;98)</small></h1>
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;99)</small></h1>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: 16</h1>
<h1 class="mvm mtn fgc1">Grid Size: 24</h1>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-comments">
</span>
<span class="mls"> icon-comments</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e95f" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe95f;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-backup">
</span>
<span class="mls"> icon-backup</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e95b" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe95b;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-support">
</span>
<span class="mls"> icon-support</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e95a" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe95a;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-RichText">
</span>
<span class="mls"> icon-control-RichText</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e939" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe939;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-download">
</span>
<span class="mls"> icon-download</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e93e" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe93e;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: 16</h1>
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-spinner2">
@ -29,7 +112,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-star-full">
@ -45,7 +128,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-star-empty">
@ -61,7 +144,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-twitter">
@ -77,7 +160,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-hour-glass">
@ -93,7 +176,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-spinner">
@ -109,7 +192,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-clock">
@ -125,7 +208,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-bin2">
@ -141,7 +224,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-earth">
@ -157,7 +240,7 @@
<input type="text" readonly value="earth, globe2" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-elapsed">
@ -173,7 +256,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-google">
@ -189,7 +272,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-lock">
@ -205,7 +288,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-microsoft">
@ -221,7 +304,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-pause">
@ -237,7 +320,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-play">
@ -253,7 +336,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-reset">
@ -269,7 +352,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-settings2">
@ -285,7 +368,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-timeout">
@ -301,7 +384,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-unlocked">
@ -320,7 +403,7 @@
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: 20</h1>
<div class="glyph fs2">
<div class="glyph fs3">
<div class="clearfix bshadow0 pbs">
<span class="icon-grid1">
@ -336,7 +419,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs3">
<div class="clearfix bshadow0 pbs">
<span class="icon-list">
@ -352,7 +435,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="glyph fs3">
<div class="clearfix bshadow0 pbs">
<span class="icon-info">
@ -368,26 +451,10 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs2">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-Stars">
</span>
<span class="mls"> icon-control-Stars</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e93a" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe93a;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: 32</h1>
<div class="glyph fs3">
<div class="glyph fs4">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-Color">
@ -403,7 +470,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs3">
<div class="glyph fs4">
<div class="clearfix bshadow0 pbs">
<span class="icon-browser">
@ -419,7 +486,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs3">
<div class="glyph fs4">
<div class="clearfix bshadow0 pbs">
<span class="icon-checkmark">
@ -435,7 +502,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs3">
<div class="glyph fs4">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-Stars">
@ -454,7 +521,7 @@
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: Unknown</h1>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-prerender">
@ -470,7 +537,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-circle">
@ -486,7 +553,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-Slug">
@ -502,7 +569,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-type-Tags">
@ -518,7 +585,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-activity">
@ -534,7 +601,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-history">
@ -550,7 +617,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-time">
@ -566,7 +633,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-add">
@ -582,7 +649,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-plus">
@ -598,7 +665,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-check-circle">
@ -614,7 +681,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-check-circle-filled">
@ -630,7 +697,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-close">
@ -646,7 +713,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-type-References">
@ -662,7 +729,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-Checkbox">
@ -678,7 +745,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-Dropdown">
@ -694,7 +761,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-Input">
@ -710,7 +777,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-Radio">
@ -726,7 +793,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-TextArea">
@ -742,7 +809,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-Toggle">
@ -758,7 +825,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-copy">
@ -774,7 +841,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-dashboard">
@ -790,7 +857,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-delete">
@ -806,7 +873,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-bin">
@ -822,7 +889,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-delete-filled">
@ -838,7 +905,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-document-delete">
@ -854,7 +921,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-document-disable">
@ -870,7 +937,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-document-publish">
@ -886,7 +953,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-drag">
@ -902,7 +969,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-filter">
@ -918,7 +985,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-github">
@ -934,7 +1001,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-help">
@ -950,7 +1017,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-location">
@ -966,7 +1033,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-Map">
@ -982,7 +1049,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-type-Geolocation">
@ -998,7 +1065,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-logo">
@ -1014,7 +1081,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-media">
@ -1030,7 +1097,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-type-Assets">
@ -1046,7 +1113,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-trigger-AssetChanged">
@ -1062,7 +1129,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-more">
@ -1078,7 +1145,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-dots">
@ -1094,7 +1161,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-pencil">
@ -1110,7 +1177,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-reference">
@ -1126,7 +1193,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-schemas">
@ -1142,7 +1209,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-search">
@ -1158,7 +1225,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-settings">
@ -1174,7 +1241,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-type-Boolean">
@ -1190,7 +1257,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-type-DateTime">
@ -1206,7 +1273,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-type-Json">
@ -1222,7 +1289,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-json">
@ -1238,7 +1305,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-type-Number">
@ -1254,7 +1321,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-type-String">
@ -1270,7 +1337,7 @@
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs4">
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-user">
@ -1287,73 +1354,6 @@
</div>
</div>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: 24</h1>
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-backup">
</span>
<span class="mls"> icon-backup</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e95b" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe95b;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-support">
</span>
<span class="mls"> icon-support</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e95a" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe95a;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-control-RichText">
</span>
<span class="mls"> icon-control-RichText</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e939" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe939;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs5">
<div class="clearfix bshadow0 pbs">
<span class="icon-download">
</span>
<span class="mls"> icon-download</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e93e" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe93e;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: 14</h1>
<div class="glyph fs6">

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.eot

Binary file not shown.

1
src/Squidex/app/theme/icomoon/fonts/icomoon.svg

@ -102,6 +102,7 @@
<glyph unicode="&#xe95c;" glyph-name="twitter" d="M1024 733.6c-37.6-16.8-78.2-28-120.6-33 43.4 26 76.6 67.2 92.4 116.2-40.6-24-85.6-41.6-133.4-51-38.4 40.8-93 66.2-153.4 66.2-116 0-210-94-210-210 0-16.4 1.8-32.4 5.4-47.8-174.6 8.8-329.4 92.4-433 219.6-18-31-28.4-67.2-28.4-105.6 0-72.8 37-137.2 93.4-174.8-34.4 1-66.8 10.6-95.2 26.2 0-0.8 0-1.8 0-2.6 0-101.8 72.4-186.8 168.6-206-17.6-4.8-36.2-7.4-55.4-7.4-13.6 0-26.6 1.4-39.6 3.8 26.8-83.4 104.4-144.2 196.2-146-72-56.4-162.4-90-261-90-17 0-33.6 1-50.2 3 93.2-59.8 203.6-94.4 322.2-94.4 386.4 0 597.8 320.2 597.8 597.8 0 9.2-0.2 18.2-0.6 27.2 41 29.4 76.6 66.4 104.8 108.6z" />
<glyph unicode="&#xe95d;" glyph-name="star-full" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538z" />
<glyph unicode="&#xe95e;" glyph-name="star-empty" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
<glyph unicode="&#xe95f;" glyph-name="comments" d="M854 256.667v512h-684v-598l86 86h598zM854 852.667c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" />
<glyph unicode="&#xe9ca;" glyph-name="earth" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512-0.002c-62.958 0-122.872 13.012-177.23 36.452l233.148 262.29c5.206 5.858 8.082 13.422 8.082 21.26v96c0 17.674-14.326 32-32 32-112.99 0-232.204 117.462-233.374 118.626-6 6.002-14.14 9.374-22.626 9.374h-128c-17.672 0-32-14.328-32-32v-192c0-12.122 6.848-23.202 17.69-28.622l110.31-55.156v-187.886c-116.052 80.956-192 215.432-192 367.664 0 68.714 15.49 133.806 43.138 192h116.862c8.488 0 16.626 3.372 22.628 9.372l128 128c6 6.002 9.372 14.14 9.372 22.628v77.412c40.562 12.074 83.518 18.588 128 18.588 70.406 0 137.004-16.26 196.282-45.2-4.144-3.502-8.176-7.164-12.046-11.036-36.266-36.264-56.236-84.478-56.236-135.764s19.97-99.5 56.236-135.764c36.434-36.432 85.218-56.264 135.634-56.26 3.166 0 6.342 0.080 9.518 0.236 13.814-51.802 38.752-186.656-8.404-372.334-0.444-1.744-0.696-3.488-0.842-5.224-81.324-83.080-194.7-134.656-320.142-134.656z" />
<glyph unicode="&#xf00a;" glyph-name="grid" d="M292.571 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf0c9;" glyph-name="list1" horiz-adv-x="878" d="M877.714 182.857v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 475.428v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 768v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571z" />

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.ttf

Binary file not shown.

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.woff

Binary file not shown.

2
src/Squidex/app/theme/icomoon/selection.json

File diff suppressed because one or more lines are too long

40
src/Squidex/app/theme/icomoon/style.css

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?z1dhmx');
src: url('fonts/icomoon.eot?z1dhmx#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?z1dhmx') format('truetype'),
url('fonts/icomoon.woff?z1dhmx') format('woff'),
url('fonts/icomoon.svg?z1dhmx#icomoon') format('svg');
src: url('fonts/icomoon.eot?jlrdyp');
src: url('fonts/icomoon.eot?jlrdyp#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?jlrdyp') format('truetype'),
url('fonts/icomoon.woff?jlrdyp') format('woff'),
url('fonts/icomoon.svg?jlrdyp#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@ -24,6 +24,21 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-comments:before {
content: "\e95f";
}
.icon-backup:before {
content: "\e95b";
}
.icon-support:before {
content: "\e95a";
}
.icon-control-RichText:before {
content: "\e939";
}
.icon-download:before {
content: "\e93e";
}
.icon-spinner2:before {
content: "\e959";
}
@ -90,9 +105,6 @@
.icon-info:before {
content: "\e93c";
}
.icon-control-Stars:before {
content: "\e93a";
}
.icon-control-Color:before {
content: "\e94d";
}
@ -261,18 +273,6 @@
.icon-user:before {
content: "\e928";
}
.icon-backup:before {
content: "\e95b";
}
.icon-support:before {
content: "\e95a";
}
.icon-control-RichText:before {
content: "\e939";
}
.icon-download:before {
content: "\e93e";
}
.icon-single-content:before {
content: "\e958";
}

Loading…
Cancel
Save