mirror of https://github.com/Squidex/squidex.git
31 changed files with 398 additions and 44 deletions
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// ContentRestored.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Contents |
|||
{ |
|||
[EventType(nameof(ContentRestored))] |
|||
public sealed class ContentRestored : ContentEvent |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// RestoreContent.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Write.Contents.Commands |
|||
{ |
|||
public sealed class RestoreContent : ContentCommand |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
<sqx-panel panelWidth="16rem"> |
|||
<div class="panel-header"> |
|||
<div class="panel-title-row"> |
|||
<h3 class="panel-title">Activity</h3> |
|||
</div> |
|||
|
|||
<a class="panel-close" sqxParentLink> |
|||
<i class="icon-close"></i> |
|||
</a> |
|||
</div> |
|||
|
|||
<div class="panel-main"> |
|||
<div class="panel-content panel-content-blank"> |
|||
<div *ngFor="let event of events | async" class="event"> |
|||
<div class="event-left"> |
|||
<img class="user-picture" [attr.title]="event.actor | sqxUserNameRef:'I'" [attr.src]="event.actor | sqxUserPictureRef" /> |
|||
</div> |
|||
<div class="event-main"> |
|||
<div class="event-message"> |
|||
<span class="event-actor user-ref">{{event.actor | sqxUserNameRef:'I'}}</span> <span [innerHTML]="format(event.message) | async"></span> |
|||
</div> |
|||
<div class="event-created">{{event.created | sqxFromNow}}</div> |
|||
|
|||
<a class="event-load">Load this Version</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</sqx-panel> |
|||
@ -0,0 +1,39 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.event { |
|||
& { |
|||
@include flex-box; |
|||
margin-bottom: 1rem; |
|||
} |
|||
|
|||
&-main { |
|||
@include flex-grow(1); |
|||
} |
|||
|
|||
&-load { |
|||
& { |
|||
font-size: .9rem; |
|||
font-weight: normal; |
|||
cursor: pointer; |
|||
color: $color-theme-blue !important; |
|||
} |
|||
|
|||
&:focus, |
|||
&:hover { |
|||
text-decoration: underline !important; |
|||
} |
|||
} |
|||
|
|||
&-left { |
|||
min-width: 2.8rem; |
|||
max-width: 2.8rem; |
|||
margin-top: .3rem; |
|||
} |
|||
|
|||
&-created { |
|||
font-size: .65rem; |
|||
font-weight: normal; |
|||
color: $color-text-decent; |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Component } from '@angular/core'; |
|||
import { ActivatedRoute } from '@angular/router'; |
|||
import { Observable } from 'rxjs'; |
|||
|
|||
import { |
|||
allParams, |
|||
AppComponentBase, |
|||
AppsStoreService, |
|||
DialogService, |
|||
HistoryChannelUpdated, |
|||
HistoryEventDto, |
|||
HistoryService, |
|||
MessageBus, |
|||
UsersProviderService |
|||
} from 'shared'; |
|||
|
|||
const REPLACEMENT_TEMP = '$TEMP$'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-history', |
|||
styleUrls: ['./content-history.component.scss'], |
|||
templateUrl: './content-history.component.html' |
|||
}) |
|||
export class ContentHistoryComponent extends AppComponentBase { |
|||
public get channel(): string { |
|||
let channelPath = this.route.snapshot.data['channel']; |
|||
|
|||
if (channelPath) { |
|||
const params = allParams(this.route); |
|||
|
|||
for (let key in params) { |
|||
if (params.hasOwnProperty(key)) { |
|||
const value = params[key]; |
|||
|
|||
channelPath = channelPath.replace(`{${key}}`, value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return channelPath; |
|||
} |
|||
|
|||
public events: Observable<HistoryEventDto[]> = |
|||
Observable.timer(0, 10000) |
|||
.merge(this.messageBus.of(HistoryChannelUpdated).delay(1000)) |
|||
.switchMap(() => this.appNameOnce()) |
|||
.switchMap(app => this.historyService.getHistory(app, this.channel).retry(2)); |
|||
|
|||
constructor(appsStore: AppsStoreService, dialogs: DialogService, |
|||
private readonly users: UsersProviderService, |
|||
private readonly historyService: HistoryService, |
|||
private readonly messageBus: MessageBus, |
|||
private readonly route: ActivatedRoute |
|||
) { |
|||
super(dialogs, appsStore); |
|||
} |
|||
|
|||
private userName(userId: string): Observable<string> { |
|||
const parts = userId.split(':'); |
|||
|
|||
if (parts[0] === 'subject') { |
|||
return this.users.getUser(parts[1], 'Me').map(u => u.displayName); |
|||
} else { |
|||
if (parts[1].endsWith('client')) { |
|||
return Observable.of(parts[1]); |
|||
} else { |
|||
return Observable.of(`${parts[1]}-client`); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public format(message: string): Observable<string> { |
|||
let foundUserId: string | null = null; |
|||
|
|||
message = message.replace(/{([^\s:]*):([^}]*)}/, (match: string, type: string, id: string) => { |
|||
if (type === 'user') { |
|||
foundUserId = id; |
|||
return REPLACEMENT_TEMP; |
|||
} else { |
|||
return id; |
|||
} |
|||
}); |
|||
|
|||
message = message.replace(/{([^}]*)}/g, (match: string, marker: string) => { |
|||
return `<span class="marker-ref">${marker}</span>`; |
|||
}); |
|||
|
|||
if (foundUserId) { |
|||
return this.userName(foundUserId).map(t => message.replace(REPLACEMENT_TEMP, `<span class="user-ref">${t}</span>`)); |
|||
} |
|||
|
|||
return Observable.of(message); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue