Browse Source

Feature/edit comments (#497)

* Edit comments.
pull/499/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
034a5b00ba
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      backend/src/Squidex.Domain.Apps.Entities/Comments/Commands/CommentTextCommand.cs
  2. 6
      backend/src/Squidex.Domain.Apps.Entities/Comments/Commands/CreateComment.cs
  3. 3
      backend/src/Squidex.Domain.Apps.Entities/Comments/Commands/UpdateComment.cs
  4. 47
      backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsCommandMiddleware.cs
  5. 17
      backend/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs
  6. 3
      frontend/app-config/webpack.config.js
  7. 4
      frontend/app/framework/angular/forms/editable-title.component.ts
  8. 79
      frontend/app/shared/components/comments/comment.component.html
  9. 8
      frontend/app/shared/components/comments/comment.component.scss
  10. 93
      frontend/app/shared/components/comments/comment.component.ts
  11. 54
      frontend/app/shared/components/comments/comments.component.html
  12. 31
      frontend/app/shared/components/comments/comments.component.ts
  13. 2
      frontend/app/shell/pages/internal/notifications-menu.component.html
  14. 4
      frontend/app/shell/pages/internal/notifications-menu.component.ts
  15. 16
      frontend/app/theme/icomoon/demo.html
  16. BIN
      frontend/app/theme/icomoon/fonts/icomoon.eot
  17. 1
      frontend/app/theme/icomoon/fonts/icomoon.svg
  18. BIN
      frontend/app/theme/icomoon/fonts/icomoon.ttf
  19. BIN
      frontend/app/theme/icomoon/fonts/icomoon.woff
  20. 2
      frontend/app/theme/icomoon/selection.json
  21. 13
      frontend/app/theme/icomoon/style.css

18
backend/src/Squidex.Domain.Apps.Entities/Comments/Commands/CommentTextCommand.cs

@ -0,0 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Entities.Comments.Commands
{
public abstract class CommentTextCommand : CommentsCommand
{
public string Text { get; set; }
public string[]? Mentions { get; set; }
}
}

6
backend/src/Squidex.Domain.Apps.Entities/Comments/Commands/CreateComment.cs

@ -9,14 +9,10 @@ using System;
namespace Squidex.Domain.Apps.Entities.Comments.Commands namespace Squidex.Domain.Apps.Entities.Comments.Commands
{ {
public sealed class CreateComment : CommentsCommand public sealed class CreateComment : CommentTextCommand
{ {
public bool IsMention { get; set; } public bool IsMention { get; set; }
public string Text { get; set; }
public string[]? Mentions { get; set; }
public Uri? Url { get; set; } public Uri? Url { get; set; }
public CreateComment() public CreateComment()

3
backend/src/Squidex.Domain.Apps.Entities/Comments/Commands/UpdateComment.cs

@ -7,8 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Comments.Commands namespace Squidex.Domain.Apps.Entities.Comments.Commands
{ {
public sealed class UpdateComment : CommentsCommand public sealed class UpdateComment : CommentTextCommand
{ {
public string Text { get; set; }
} }
} }

47
backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsCommandMiddleware.cs

@ -43,23 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Comments
{ {
if (commentsCommand is CreateComment createComment && !IsMention(createComment)) if (commentsCommand is CreateComment createComment && !IsMention(createComment))
{ {
await MentionUsersAsync(createComment); await ReplicateCommandAsync(context, createComment);
if (createComment.Mentions != null)
{
foreach (var userId in createComment.Mentions)
{
var notificationCommand = SimpleMapper.Map(createComment, new CreateComment());
notificationCommand.AppId = null!;
notificationCommand.Mentions = null;
notificationCommand.CommentsId = userId;
notificationCommand.ExpectedVersion = EtagVersion.Any;
notificationCommand.IsMention = true;
context.CommandBus.PublishAsync(notificationCommand).Forget();
}
}
} }
await ExecuteCommandAsync(context, commentsCommand); await ExecuteCommandAsync(context, commentsCommand);
@ -68,6 +52,27 @@ namespace Squidex.Domain.Apps.Entities.Comments
await next(context); await next(context);
} }
private async Task ReplicateCommandAsync(CommandContext context, CommentTextCommand command)
{
await MentionUsersAsync(command);
if (command.Mentions != null)
{
foreach (var userId in command.Mentions)
{
var notificationCommand = SimpleMapper.Map(command, new CreateComment());
notificationCommand.AppId = null!;
notificationCommand.Mentions = null;
notificationCommand.CommentsId = userId;
notificationCommand.ExpectedVersion = EtagVersion.Any;
notificationCommand.IsMention = true;
context.CommandBus.PublishAsync(notificationCommand).Forget();
}
}
}
private async Task ExecuteCommandAsync(CommandContext context, CommentsCommand commentsCommand) private async Task ExecuteCommandAsync(CommandContext context, CommentsCommand commentsCommand)
{ {
var grain = grainFactory.GetGrain<ICommentsGrain>(commentsCommand.CommentsId); var grain = grainFactory.GetGrain<ICommentsGrain>(commentsCommand.CommentsId);
@ -82,11 +87,11 @@ namespace Squidex.Domain.Apps.Entities.Comments
return createComment.IsMention; return createComment.IsMention;
} }
private async Task MentionUsersAsync(CreateComment createComment) private async Task MentionUsersAsync(CommentTextCommand command)
{ {
if (!string.IsNullOrWhiteSpace(createComment.Text)) if (!string.IsNullOrWhiteSpace(command.Text))
{ {
var emails = MentionRegex.Matches(createComment.Text).Select(x => x.Value.Substring(1)).ToArray(); var emails = MentionRegex.Matches(command.Text).Select(x => x.Value.Substring(1)).ToArray();
if (emails.Length > 0) if (emails.Length > 0)
{ {
@ -104,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.Comments
if (mentions.Count > 0) if (mentions.Count > 0)
{ {
createComment.Mentions = mentions.ToArray(); command.Mentions = mentions.ToArray();
} }
} }
} }

17
backend/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs

@ -11,6 +11,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using Squidex.Areas.Api.Controllers.Users.Models; using Squidex.Areas.Api.Controllers.Users.Models;
using Squidex.Domain.Users; using Squidex.Domain.Users;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
@ -27,6 +28,7 @@ namespace Squidex.Areas.Api.Controllers.Users
public sealed class UsersController : ApiController public sealed class UsersController : ApiController
{ {
private static readonly byte[] AvatarBytes; private static readonly byte[] AvatarBytes;
private readonly IHttpClientFactory httpClientFactory;
private readonly IUserPictureStore userPictureStore; private readonly IUserPictureStore userPictureStore;
private readonly IUserResolver userResolver; private readonly IUserResolver userResolver;
private readonly ISemanticLog log; private readonly ISemanticLog log;
@ -45,11 +47,13 @@ namespace Squidex.Areas.Api.Controllers.Users
public UsersController( public UsersController(
ICommandBus commandBus, ICommandBus commandBus,
IHttpClientFactory httpClientFactory,
IUserPictureStore userPictureStore, IUserPictureStore userPictureStore,
IUserResolver userResolver, IUserResolver userResolver,
ISemanticLog log) ISemanticLog log)
: base(commandBus) : base(commandBus)
{ {
this.httpClientFactory = httpClientFactory;
this.userPictureStore = userPictureStore; this.userPictureStore = userPictureStore;
this.userResolver = userResolver; this.userResolver = userResolver;
@ -177,7 +181,7 @@ namespace Squidex.Areas.Api.Controllers.Users
}); });
} }
using (var client = new HttpClient()) using (var client = httpClientFactory.CreateClient())
{ {
var url = entity.PictureNormalizedUrl(); var url = entity.PictureNormalizedUrl();
@ -189,7 +193,16 @@ namespace Squidex.Areas.Api.Controllers.Users
{ {
var contentType = response.Content.Headers.ContentType.ToString(); var contentType = response.Content.Headers.ContentType.ToString();
return new FileStreamResult(await response.Content.ReadAsStreamAsync(), contentType); var etag = response.Headers.ETag;
var result = new FileStreamResult(await response.Content.ReadAsStreamAsync(), contentType);
if (!string.IsNullOrWhiteSpace(etag?.Tag))
{
result.EntityTag = new EntityTagHeaderValue(etag.Tag, etag.IsWeak);
}
return result;
} }
} }
} }

3
frontend/app-config/webpack.config.js

@ -40,6 +40,7 @@ module.exports = function (env) {
const isTests = env && env.target === 'tests'; const isTests = env && env.target === 'tests';
const isTestCoverage = env && env.coverage; const isTestCoverage = env && env.coverage;
const isAnalyzing = isProduction && env.analyze; const isAnalyzing = isProduction && env.analyze;
const isAot = isProduction;
const configFile = isTests ? 'tsconfig.spec.json' : 'tsconfig.app.json'; const configFile = isTests ? 'tsconfig.spec.json' : 'tsconfig.app.json';
@ -296,7 +297,7 @@ module.exports = function (env) {
directTemplateLoading: true, directTemplateLoading: true,
entryModule: 'app/app.module#AppModule', entryModule: 'app/app.module#AppModule',
sourceMap: !isProduction, sourceMap: !isProduction,
skipCodeGeneration: false, skipCodeGeneration: !isAot,
tsConfigPath: configFile tsConfigPath: configFile
}) })
); );

4
frontend/app/framework/angular/forms/editable-title.component.ts

@ -8,7 +8,7 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms'; import { FormBuilder, Validators } from '@angular/forms';
const ESCAPE_KEY = 27; import { Keys } from '@app/framework/internal';
@Component({ @Component({
selector: 'sqx-editable-title', selector: 'sqx-editable-title',
@ -56,7 +56,7 @@ export class EditableTitleComponent {
} }
public onKeyDown(keyCode: number) { public onKeyDown(keyCode: number) {
if (keyCode === ESCAPE_KEY) { if (keyCode === Keys.ESCAPE) {
this.toggleRename(); this.toggleRename();
} }
} }

79
frontend/app/shared/components/comments/comment.component.html

@ -1,30 +1,65 @@
<div class="comment row no-gutters"> <div class="comment row no-gutters">
<div class="col-auto"> <div class="col-auto pr-2">
<img class="user-picture" title="{{comment.user | sqxUserNameRef}}" [src]="comment.user | sqxUserPictureRef" /> <img class="user-picture" title="{{comment.user | sqxUserNameRef}}" [src]="comment.user | sqxUserPictureRef" />
</div> </div>
<div class="col pl-2 col-right">
<div class="comment-message">
<div class="user-row">
<div class="user-ref">{{comment.user | sqxUserNameRef}}</div>
</div>
<div [innerHTML]="comment.text | sqxMarkdown"></div>
<div class="comment-created text-muted">
<ng-container *ngIf="canFollow && comment.url">
<a [routerLink]="comment.url">Follow</a>&nbsp;
</ng-container>
{{comment.time | sqxFromNow}} <ng-container *ngIf="!isEditing; else editing">
<div class="col col-text">
<div class="comment-message">
<div class="user-row">
<div class="user-ref">{{comment.user | sqxUserNameRef}}</div>
</div>
<div [innerHTML]="comment.text | sqxMarkdown"></div>
<div class="comment-created text-muted">
<ng-container *ngIf="canFollow && comment.url">
<a [routerLink]="comment.url">Follow</a>&nbsp;
</ng-container>
{{comment.time | sqxFromNow}}
</div>
</div> </div>
</div> </div>
</ng-container>
<ng-template #editing>
<div class="col">
<form (ngSubmit)="update()">
<textarea class="form-control mb-1" name="{{comment.id}}" sqxFocusOnInit
[(ngModel)]="editingText"
[mention]="mentionUsers"
[mentionConfig]="mentionConfig"
[mentionListTemplate]="mentionListTemplate"
(keydown)="updateWhenEnter($event)"></textarea>
<div>
<button type="button" class="btn btn-sm btn-secondary mr-1" (click)="cancelEdit()">
Cancel
</button>
<button type="submit" class="btn btn-sm btn-primary">
<i class="icon-enter"></i> Save
</button>
</div>
</form>
</div>
</ng-template>
<div class="actions" *ngIf="!isEditing">
<button *ngIf="isEditable && canEdit" type="button" class="btn btn-sm btn-text-secondary" (click)="startEdit()">
<i class="icon-pencil"></i>
</button>
<button *ngIf="isDeletable || canDelete" type="button" class="btn btn-sm btn-text-danger"
(sqxConfirmClick)="delete()"
confirmTitle="Delete comment"
confirmText="Do you really want to delete the comment?"
[confirmRequired]="confirmDelete">
<i class="icon-bin2"></i>
</button>
</div> </div>
</div>
<button *ngIf="comment.user === userToken || canDelete" type="button" class="btn btn-sm btn-text-danger item-remove" <ng-template #mentionListTemplate let-item="item">
(sqxConfirmClick)="delete.emit()" {{item['contributorEmail']}}
confirmTitle="Delete comment" </ng-template>
confirmText="Do you really want to delete the comment?"
[confirmRequired]="confirmDelete">
<i class="icon-bin2"></i>
</button>
</div>

8
frontend/app/shared/components/comments/comment.component.scss

@ -1,5 +1,7 @@
.item-remove { .actions {
@include absolute(-5px, -15px, auto, auto); @include absolute(-5px, -15px, auto, auto);
background: $color-table-background;
border: 0;
display: none; display: none;
} }
@ -17,7 +19,7 @@
} }
} }
.col-right { .col-text {
overflow-wrap: break-word; overflow-wrap: break-word;
overflow-x: hidden; overflow-x: hidden;
} }
@ -41,7 +43,7 @@
} }
&:hover { &:hover {
.item-remove { .actions {
display: block; display: block;
} }
} }

93
frontend/app/shared/components/comments/comment.component.ts

@ -5,9 +5,16 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { MentionConfig } from 'angular-mentions';
import { CommentDto } from '@app/shared/internal'; import {
CommentDto,
CommentsState,
ContributorDto,
DialogService,
Keys
} from '@app/shared/internal';
@Component({ @Component({
selector: 'sqx-comment', selector: 'sqx-comment',
@ -15,15 +22,18 @@ import { CommentDto } from '@app/shared/internal';
templateUrl: './comment.component.html', templateUrl: './comment.component.html',
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class CommentComponent { export class CommentComponent implements OnChanges {
@Output() @Input()
public delete = new EventEmitter(); public canFollow = false;
@Input() @Input()
public canDelete = false; public canDelete = false;
@Input() @Input()
public canFollow = false; public canEdit = false;
@Input()
public commentsState: CommentsState;
@Input() @Input()
public confirmDelete = true; public confirmDelete = true;
@ -33,4 +43,75 @@ export class CommentComponent {
@Input() @Input()
public userToken: string; public userToken: string;
@Input()
public mentionUsers: ReadonlyArray<ContributorDto>;
public mentionConfig: MentionConfig = { dropUp: true, labelKey: 'contributorEmail' };
public isDeletable = false;
public isEditable = false;
public isEditing = false;
public editingText: string;
constructor(
private readonly dialogs: DialogService
) {
}
public ngOnChanges() {
const isMyComment = this.comment.user === this.userToken;
this.isDeletable = isMyComment;
this.isEditable = isMyComment;
}
public startEdit() {
this.editingText = this.comment.text;
this.isEditing = true;
}
public cancelEdit() {
this.isEditing = false;
}
public delete() {
if (!this.isDeletable) {
return;
}
this.commentsState.delete(this.comment);
}
public updateWhenEnter(event: KeyboardEvent) {
if (event.keyCode === Keys.ENTER && !event.altKey && !event.shiftKey && !event.defaultPrevented) {
event.preventDefault();
event.stopImmediatePropagation();
event.stopPropagation();
this.update();
}
}
public update() {
if (!this.isEditable) {
return;
}
const text = this.editingText;
if (!text || text.length === 0) {
this.dialogs.confirm('Delete comment', 'Do you really want to delete the comment?')
.subscribe(() => {
this.delete();
});
} else {
this.commentsState.update(this.comment, text);
this.cancelEdit();
}
}
} }

54
frontend/app/shared/components/comments/comments.component.html

@ -4,31 +4,37 @@
</ng-container> </ng-container>
<ng-container content> <ng-container content>
<div class="comments-list" #scrollMe [scrollTop]="scrollMe.scrollHeight"> <ng-container *ngIf="mentionUsers | async; let users">
<sqx-comment *ngFor="let comment of commentsState.comments | async; trackBy: trackByComment"
[comment]="comment" <div class="comments-list" #commentsList>
[canDelete]="true" <div (sqxResized)="scrollDown()">
[canFollow]="false" <sqx-comment *ngFor="let comment of commentsState.comments | async; trackBy: trackByComment"
(delete)="delete(comment)" [comment]="comment"
[userToken]="userToken"> [commentsState]="commentsState"
</sqx-comment> [mentionUsers]="users"
</div> [canEdit]="true"
[canFollow]="false"
<div class="comments-footer"> [userToken]="userToken">
<ng-template #mentionListTemplate let-item="item"> </sqx-comment>
{{item['contributorEmail']}} </div>
</ng-template> </div>
<form [formGroup]="commentForm.form" (ngSubmit)="comment()"> <div class="comments-footer">
<input class="form-control" name="text" formControlName="text" placeholder="Create a comment" <ng-template #mentionListTemplate let-item="item">
[mention]="mentionUsers | async" {{item['contributorEmail']}}
[mentionConfig]="mentionConfig" </ng-template>
[mentionListTemplate]="mentionListTemplate"
autocomplete="off" <form [formGroup]="commentForm.form" (ngSubmit)="comment()">
autocorrect="off" <input class="form-control" name="text" formControlName="text" placeholder="Create a comment"
autocapitalize="off" /> [mention]="users"
</form> [mentionConfig]="mentionConfig"
</div> [mentionListTemplate]="mentionListTemplate"
autocomplete="off"
autocorrect="off"
autocapitalize="off" />
</form>
</div>
</ng-container>
</ng-container> </ng-container>
</sqx-panel> </sqx-panel>

31
frontend/app/shared/components/comments/comments.component.ts

@ -5,9 +5,10 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, ElementRef, Input, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { MentionConfig } from 'angular-mentions';
import { timer } from 'rxjs'; import { timer } from 'rxjs';
import { onErrorResumeNext, switchMap } from 'rxjs/operators'; import { onErrorResumeNext, switchMap } from 'rxjs/operators';
@ -23,12 +24,20 @@ import {
UpsertCommentForm UpsertCommentForm
} from '@app/shared/internal'; } from '@app/shared/internal';
import { CommentComponent } from './comment.component';
@Component({ @Component({
selector: 'sqx-comments', selector: 'sqx-comments',
styleUrls: ['./comments.component.scss'], styleUrls: ['./comments.component.scss'],
templateUrl: './comments.component.html' templateUrl: './comments.component.html'
}) })
export class CommentsComponent extends ResourceOwner implements OnInit { export class CommentsComponent extends ResourceOwner implements OnInit {
@ViewChild('commentsList', { static: false })
public commentsList: ElementRef<HTMLDivElement>;
@ViewChildren(CommentComponent)
public children: QueryList<CommentComponent>;
@Input() @Input()
public commentsId: string; public commentsId: string;
@ -37,7 +46,7 @@ export class CommentsComponent extends ResourceOwner implements OnInit {
public commentForm = new UpsertCommentForm(this.formBuilder); public commentForm = new UpsertCommentForm(this.formBuilder);
public mentionUsers = this.contributorsState.contributors; public mentionUsers = this.contributorsState.contributors;
public mentionConfig = { dropUp: true, labelKey: 'contributorEmail' }; public mentionConfig: MentionConfig = { dropUp: true, labelKey: 'contributorEmail' };
public userToken: string; public userToken: string;
@ -64,12 +73,20 @@ export class CommentsComponent extends ResourceOwner implements OnInit {
this.own(timer(0, 4000).pipe(switchMap(() => this.commentsState.load(true).pipe(onErrorResumeNext())))); this.own(timer(0, 4000).pipe(switchMap(() => this.commentsState.load(true).pipe(onErrorResumeNext()))));
} }
public delete(comment: CommentDto) { public scrollDown() {
this.commentsState.delete(comment); if (this.commentsList && this.commentsList.nativeElement) {
} let isEditing = false;
public update(comment: CommentDto, text: string) { this.children.forEach(x => {
this.commentsState.update(comment, text); isEditing = isEditing || x.isEditing;
});
if (!isEditing) {
const height = this.commentsList.nativeElement.scrollHeight;
this.commentsList.nativeElement.scrollTop = height;
}
}
} }
public comment() { public comment() {

2
frontend/app/shell/pages/internal/notifications-menu.component.html

@ -17,10 +17,10 @@
<sqx-comment *ngFor="let comment of comments; trackBy: trackByComment" <sqx-comment *ngFor="let comment of comments; trackBy: trackByComment"
[comment]="comment" [comment]="comment"
[commentsState]="commentsState"
[confirmDelete]="false" [confirmDelete]="false"
[canDelete]="true" [canDelete]="true"
[canFollow]="true" [canFollow]="true"
(delete)="delete(comment)"
[userToken]="userToken"> [userToken]="userToken">
</sqx-comment> </sqx-comment>
</ng-container> </ng-container>

4
frontend/app/shell/pages/internal/notifications-menu.component.ts

@ -92,10 +92,6 @@ export class NotificationsMenuComponent extends ResourceOwner implements OnInit
this.own(timer(0, 4000).pipe(switchMap(() => this.commentsState.load(true).pipe(onErrorResumeNext())))); this.own(timer(0, 4000).pipe(switchMap(() => this.commentsState.load(true).pipe(onErrorResumeNext()))));
} }
public delete(comment: CommentDto) {
this.commentsState.delete(comment);
}
public trackByComment(comment: CommentDto) { public trackByComment(comment: CommentDto) {
return comment.id; return comment.id;
} }

16
frontend/app/theme/icomoon/demo.html

@ -9,10 +9,24 @@
<link rel="stylesheet" href="style.css"></head> <link rel="stylesheet" href="style.css"></head>
<body> <body>
<div class="bgc1 clearfix"> <div class="bgc1 clearfix">
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;135)</small></h1> <h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;136)</small></h1>
</div> </div>
<div class="clearfix mhl ptl"> <div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: 24</h1> <h1 class="mvm mtn fgc1">Grid Size: 24</h1>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-enter"></span>
<span class="mls"> icon-enter</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e984" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe984;" 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="glyph fs1">
<div class="clearfix bshadow0 pbs"> <div class="clearfix bshadow0 pbs">
<span class="icon-zoom_out"></span> <span class="icon-zoom_out"></span>

BIN
frontend/app/theme/icomoon/fonts/icomoon.eot

Binary file not shown.

1
frontend/app/theme/icomoon/fonts/icomoon.svg

@ -139,6 +139,7 @@
<glyph unicode="&#xe981;" glyph-name="rotate_left" d="M554 764.667q126-16 213-112t87-226-87-226-213-112v86q92 16 153 87t61 165-61 165-153 87v-166l-194 190 194 194v-132zM302 156.667l62 62q46-34 106-44v-86q-96 12-168 68zM260 384.667q10-58 42-106l-60-60q-56 74-68 166h86zM304 574.667q-36-52-44-106h-86q12 90 70 166z" /> <glyph unicode="&#xe981;" glyph-name="rotate_left" d="M554 764.667q126-16 213-112t87-226-87-226-213-112v86q92 16 153 87t61 165-61 165-153 87v-166l-194 190 194 194v-132zM302 156.667l62 62q46-34 106-44v-86q-96 12-168 68zM260 384.667q10-58 42-106l-60-60q-56 74-68 166h86zM304 574.667q-36-52-44-106h-86q12 90 70 166z" />
<glyph unicode="&#xe982;" glyph-name="zoom_out" d="M298 554.667h214v-42h-214v42zM406 340.667q80 0 136 56t56 136-56 136-136 56-136-56-56-136 56-136 136-56zM662 340.667l212-212-64-64-212 212v34l-12 12q-76-66-180-66-116 0-197 80t-81 196 81 197 197 81 196-81 80-197q0-42-20-95t-46-85l12-12h34z" /> <glyph unicode="&#xe982;" glyph-name="zoom_out" d="M298 554.667h214v-42h-214v42zM406 340.667q80 0 136 56t56 136-56 136-136 56-136-56-56-136 56-136 136-56zM662 340.667l212-212-64-64-212 212v34l-12 12q-76-66-180-66-116 0-197 80t-81 196 81 197 197 81 196-81 80-197q0-42-20-95t-46-85l12-12h34z" />
<glyph unicode="&#xe983;" glyph-name="zoom_in" d="M512 512.667h-86v-86h-42v86h-86v42h86v86h42v-86h86v-42zM406 340.667q80 0 136 56t56 136-56 136-136 56-136-56-56-136 56-136 136-56zM662 340.667l212-212-64-64-212 212v34l-12 12q-76-66-180-66-116 0-197 80t-81 196 81 197 197 81 196-81 80-197q0-42-20-95t-46-85l12-12h34z" /> <glyph unicode="&#xe983;" glyph-name="zoom_in" d="M512 512.667h-86v-86h-42v86h-86v42h86v86h42v-86h86v-42zM406 340.667q80 0 136 56t56 136-56 136-136 56-136-56-56-136 56-136 136-56zM662 340.667l212-212-64-64-212 212v34l-12 12q-76-66-180-66-116 0-197 80t-81 196 81 197 197 81 196-81 80-197q0-42-20-95t-46-85l12-12h34z" />
<glyph unicode="&#xe984;" glyph-name="enter" d="M470 554.667l60-60-154-154h392v428h86v-512h-478l154-154-60-60-256 256z" />
<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="&#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="&#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" /> <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: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

BIN
frontend/app/theme/icomoon/fonts/icomoon.ttf

Binary file not shown.

BIN
frontend/app/theme/icomoon/fonts/icomoon.woff

Binary file not shown.

2
frontend/app/theme/icomoon/selection.json

File diff suppressed because one or more lines are too long

13
frontend/app/theme/icomoon/style.css

@ -1,10 +1,10 @@
@font-face { @font-face {
font-family: 'icomoon'; font-family: 'icomoon';
src: url('fonts/icomoon.eot?fxsj8g'); src: url('fonts/icomoon.eot?uop9lr');
src: url('fonts/icomoon.eot?fxsj8g#iefix') format('embedded-opentype'), src: url('fonts/icomoon.eot?uop9lr#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?fxsj8g') format('truetype'), url('fonts/icomoon.ttf?uop9lr') format('truetype'),
url('fonts/icomoon.woff?fxsj8g') format('woff'), url('fonts/icomoon.woff?uop9lr') format('woff'),
url('fonts/icomoon.svg?fxsj8g#icomoon') format('svg'); url('fonts/icomoon.svg?uop9lr#icomoon') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-display: block; font-display: block;
@ -25,6 +25,9 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-enter:before {
content: "\e984";
}
.icon-zoom_out:before { .icon-zoom_out:before {
content: "\e982"; content: "\e982";
} }

Loading…
Cancel
Save