output revisions for pages

This commit is contained in:
2020-08-17 14:46:13 +02:00
parent d5f7f3ddd8
commit 91548e2cd0
15 changed files with 330 additions and 71 deletions
-17
View File
@@ -71,18 +71,6 @@ namespace zero.Core.Api
}
/// <inheritdoc />
public async Task<ListResult<T>> GetRevisions(string id, int pageNumber = 1, int pageSize = 30)
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
List<T> revisions = await session.Advanced.Revisions.GetForAsync<T>(id, pageNumber - 1, pageSize);
return new ListResult<T>(revisions, revisions.Count, pageNumber, pageSize);
}
}
/// <inheritdoc />
public async Task<EntityResult<T>> Save(T model)
{
@@ -310,11 +298,6 @@ namespace zero.Core.Api
/// </summary>
PageType GetPageType(string alias);
/// <summary>
/// Get revisions of a page (if activated in RavenDB configuration)
/// </summary>
Task<ListResult<T>> GetRevisions(string id, int pageNumber = 1, int pageSize = 30);
/// <summary>
/// Creates or updates a page
/// </summary>
+77
View File
@@ -0,0 +1,77 @@
using Newtonsoft.Json;
using Raven.Client.Documents;
using Raven.Client.Documents.Session;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using zero.Core.Entities;
using zero.Core.Extensions;
using zero.Core.Options;
namespace zero.Core.Api
{
public class RevisionsApi : IRevisionsApi
{
protected IZeroOptions Options { get; set; }
protected IDocumentStore Raven { get; set; }
public RevisionsApi(IZeroOptions options, IDocumentStore raven)
{
Options = options;
Raven = raven;
}
/// <summary>
/// Get revision list for an entity
/// </summary>
public async Task<ListResult<Revision>> GetPaged<T>(string id, int pageNumber = 1, int pageSize = 30, bool includeContent = false) where T : IZeroEntity
{
using IAsyncDocumentSession session = Raven.OpenAsyncSession();
List<T> items = await session.Advanced.Revisions.GetForAsync<T>(id, pageNumber - 1, pageSize);
List<Revision> revisions = new List<Revision>();
string[] userIds = items.Select(x => x.LastModifiedById).Distinct().ToArray();
Dictionary<string, User> users = await session.LoadAsync<User>(userIds);
foreach (T item in items)
{
DateTime? date = session.Advanced.GetLastModifiedFor(item);
Revision revision = new Revision()
{
ChangeVector = session.Advanced.GetChangeVectorFor(item),
Date = date.HasValue ? new DateTimeOffset(date.Value) : item.CreatedDate,
Json = includeContent ? JsonConvert.SerializeObject(item) : null
};
if (!item.LastModifiedById.IsNullOrEmpty() && users.TryGetValue(item.LastModifiedById, out User user))
{
revision.User = new RevisionUser()
{
AvatarId = user.AvatarId,
Id = user.Id,
Name = user.Name
};
}
revisions.Add(revision);
}
return new ListResult<Revision>(revisions, revisions.Count, pageNumber, pageSize);
}
}
public interface IRevisionsApi
{
/// <summary>
/// Get revision list (without content JSON) for an entity
/// </summary>
Task<ListResult<Revision>> GetPaged<T>(string id, int pageNumber = 1, int pageSize = 30, bool includeContent = false) where T : IZeroEntity;
}
}
+19 -24
View File
@@ -1,29 +1,24 @@
//using System;
using System;
//namespace zero.Core.Entities
//{
// public class Revision : ZeroEntity, IRevision
// {
// /// <inheritdoc />
// public string Content { get; set; }
// }
namespace zero.Core.Entities
{
public class Revision
{
public RevisionUser User { get; set; }
public DateTimeOffset Date { get; set; }
// public interface IRevision : IZeroEntity, IZeroDbConventions
// {
// /// <summary>
// /// Id of the affected entity
// /// </summary>
// string EntityId { get; set; }
public string ChangeVector { get; set; }
// /// <summary>
// /// Id of the affected entity
// /// </summary>
// string EntityId { get; set; }
public string Json { get; set; }
}
// /// <summary>
// /// Contains the content as JSON
// /// </summary>
// string Content { get; set; }
// }
//}
public class RevisionUser
{
public string Id { get; set; }
public string Name { get; set; }
public string AvatarId { get; set; }
}
}
@@ -0,0 +1,49 @@
using Raven.Client.Documents.Session;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using zero.Core.Entities;
namespace zero.Core.Extensions
{
public static class RavenDocumentSessionExtensions
{
/// <summary>
/// Get revision list (without content JSON) for an entity
/// </summary>
public static async Task<ListResult<Revision>> GetRevisions<T>(this IAsyncDocumentSession session, string id, int pageNumber = 1, int pageSize = 30) where T : IZeroEntity
{
List<T> items = await session.Advanced.Revisions.GetForAsync<T>(id, pageNumber - 1, pageSize);
List<Revision> revisions = new List<Revision>();
string[] userIds = items.Select(x => x.LastModifiedById).Distinct().ToArray();
Dictionary<string, User> users = await session.LoadAsync<User>(userIds);
foreach (T item in items)
{
DateTime? date = session.Advanced.GetLastModifiedFor(item);
Revision revision = new Revision()
{
ChangeVector = session.Advanced.GetChangeVectorFor(item),
Date = date.HasValue ? new DateTimeOffset(date.Value) : item.CreatedDate
};
if (!item.LastModifiedById.IsNullOrEmpty() && users.TryGetValue(item.LastModifiedById, out User user))
{
revision.User = new RevisionUser()
{
AvatarId = user.AvatarId,
Id = user.Id,
Name = user.Name
};
}
revisions.Add(revision);
}
return new ListResult<Revision>(revisions, revisions.Count, pageNumber, pageSize);
}
}
}
@@ -12,7 +12,7 @@ using zero.Core.Utils;
namespace zero.Core.Extensions
{
public static class DocumentStoreExtensions
public static class RavenDocumentStoreExtensions
{
/// <summary>
/// Setup conventions for the document store
+20 -2
View File
@@ -18,6 +18,10 @@
format: {
type: String,
default: null
},
split: {
type: Boolean,
default: false
}
},
@@ -47,10 +51,24 @@
return;
}
this.output = Strings.date(this.value, this.format);
if (!this.split)
{
this.output = Strings.date(this.value, this.format);
}
else
{
this.output = Strings.date(this.value, 'short') + ' <span class="-minor">' + Strings.date(this.value, 'time') + '</span>';
}
}
}
}
</script>
</script>
<style lang="scss">
.ui-date .-minor
{
color: var(--color-fg-dim);
}
</style>
+112
View File
@@ -0,0 +1,112 @@
<template>
<div class="ui-revisions" :class="{ 'is-empty': value.items.length < 1 }">
<div class="ui-revision" v-for="revision in value.items">
<span class="ui-revision-action">updated</span>
<ui-date class="ui-revision-date" v-model="revision.date" format="long" :split="true" />
<router-link :to="{ name: userRoute, params: { id: revision.user.id }}" v-if="revision.user" class="ui-revision-user">
<img class="ui-revision-user-image" v-if="revision.user" :src="getImage(revision.user.avatarId)" :alt="revision.user.name" />
<span v-if="revision.user" class="ui-revision-user-name">{{revision.user.name}}</span>
</router-link>
<div v-else></div>
<button class="ui-link">View</button>
</div>
</div>
</template>
<script>
import dayjs from 'dayjs';
import Strings from 'zero/services/strings';
import MediaApi from 'zero/resources/media.js';
export default {
name: 'uiRevisions',
props: {
value: {
type: Object,
default: () => ({
totalItems: 0,
items: []
})
}
},
data: () => ({
userRoute: zero.alias.sections.settings + '-' + zero.alias.settings.users + '-edit'
}),
methods: {
getImage(id)
{
return MediaApi.getImageSource(id);
}
}
}
</script>
<style lang="scss">
.ui-revision
{
display: grid;
grid-template-columns: auto 3fr 2fr auto;
grid-gap: 20px;
align-items: center;
min-height: 28px;
& + .ui-revision
{
margin-top: 24px;
}
&:first-child .ui-revision-action:before
{
display: none;
}
}
.ui-revision-action
{
align-self: center;
display: inline-block;
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
background: var(--color-accent-info-bg);
color: var(--color-accent-info);
height: 22px;
line-height: 22px;
padding: 0 10px;
border-radius: 16px;
letter-spacing: .5px;
position: relative;
&:before
{
content: '';
position: absolute;
left: calc(50% - 1.5px);
top: -30px;
height: 30px;
width: 3px;
background: var(--color-accent-info-bg);
}
}
.ui-revision-user
{
color: var(--color-fg);
}
.ui-revision-user-image
{
width: 28px;
height: 28px;
border-radius: 14px;
margin-right: 8px;
}
</style>
+28 -5
View File
@@ -1,7 +1,11 @@
<template>
<div class="page-editor-info ui-view-box has-sidebar">
<div class="ui-box">
box
<div>
<div class="ui-box"></div>
<div class="ui-box">
<h3 class="ui-headline">Revisions</h3>
<ui-revisions v-model="revisions" />
</div>
</div>
<div class="ui-view-box-aside editor-infos">
<div class="ui-box editor-active-toggle" :class="{'is-active': value.isActive }">
@@ -13,10 +17,10 @@
</ui-property>
</div>
<div class="ui-box">
<ui-property v-if="value.id" label="@ui.id" :is-text="true">
<ui-property v-if="!isCreate" label="@ui.id" :is-text="true">
{{value.id}}
</ui-property>
<ui-property v-if="value.id" label="@ui.createdDate" :is-text="true">
<ui-property v-if="!isCreate" label="@ui.createdDate" :is-text="true">
<ui-date v-model="value.createdDate" />
</ui-property>
<ui-property label="Type" :is-text="true" v-if="pageType">
@@ -39,15 +43,34 @@
},
data: () => ({
pageType: null
pageType: null,
revisions: {
totalItems: 0,
items: []
}
}),
computed: {
isCreate()
{
return !this.value.id;
}
},
mounted()
{
PagesApi.getPageType(this.value.pageTypeAlias).then(pageType =>
{
this.pageType = pageType;
});
if (!this.isCreate)
{
PagesApi.getRevisions(this.value.id).then(response =>
{
this.revisions = response;
});
}
},
+1 -6
View File
@@ -6,7 +6,7 @@
</template>
</ui-form-header>
<ui-editor v-if="!loading" :config="renderer" v-model="model" :meta="meta" :is-page="true" infos="none" :on-configure="onEditorConfigure" />
<ui-editor v-if="!loading" :config="renderer" v-model="model" :meta="meta" :is-page="true" infos="none" :on-configure="onEditorConfigure" :active-tab="2" />
</ui-form>
</template>
@@ -82,11 +82,6 @@
onLoad(form)
{
PagesApi.getRevisions(this.id).then(response =>
{
console.info(response);
});
form.load(!this.id ? PagesApi.getEmpty(this.type, this.parent) : PagesApi.getById(this.id)).then(response =>
{
this.renderer = 'page.' + response.entity.pageTypeAlias;
+6 -1
View File
@@ -2,8 +2,9 @@ import dayjs from 'dayjs';
const BYTE_UNIT = 'B';
const UNITS = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const DATETIME_FORMAT = 'DD.MM.YY HH:mm';
const DATE_FORMAT = 'DD.MM.YY';
const TIME_FORMAT = 'HH:mm';
const DATETIME_FORMAT = DATE_FORMAT + ' ' + TIME_FORMAT;
export default {
/// <summary>
@@ -59,6 +60,10 @@ export default {
{
format = DATE_FORMAT;
}
else if (format === 'time')
{
format = TIME_FORMAT;
}
return dayjs(value).format(format);
},
@@ -99,6 +99,12 @@ button::-moz-focus-inner
padding: 0 30px;
}
&.type-small
{
height: 32px;
padding: 0 12px;
}
&.type-light
{
background: var(--color-bg-bright-two);
+4 -12
View File
@@ -9,19 +9,11 @@
border-radius: var(--radius);
box-shadow: var(--color-shadow-short);
/*max-width: 1104px;*/
h2
h2, h3
{
margin: -5px -32px var(--padding);
padding: 0 var(--padding) 25px;
//border-bottom: 1px solid var(--color-line-light);
}
h3
{
margin: -5px -32px var(--padding);
padding: 0 var(--padding) 25px;
//border-bottom: 1px solid var(--color-line-light);
margin: -5px 0 25px;
padding: 0;
}
.ui-property-content &
+2 -1
View File
@@ -3,5 +3,6 @@
.ui-link
{
color: var(--color-primary);
text-decoration: underline dotted;
text-decoration: underline dotted var(--color-fg-dim-two);
text-underline-offset: 3px;
}
+4 -2
View File
@@ -8,10 +8,12 @@ namespace zero.Web.Controllers
public class PagesController<T> : BackofficeController where T : IPage, new()
{
IPagesApi<T> Api;
IRevisionsApi RevisionsApi;
public PagesController(IPagesApi<T> api)
public PagesController(IPagesApi<T> api, IRevisionsApi revisionsApi)
{
Api = api;
RevisionsApi = revisionsApi;
}
@@ -28,7 +30,7 @@ namespace zero.Web.Controllers
ParentId = parent
});
public async Task<IActionResult> GetRevisions([FromQuery] string id) => Json(await Api.GetRevisions(id));
public async Task<IActionResult> GetRevisions([FromQuery] string id) => Json(await RevisionsApi.GetPaged<T>(id));
public async Task<IActionResult> Save([FromBody] T model) => Json(await Api.Save(model));
@@ -43,6 +43,7 @@ namespace zero.Web.Defaults
services.AddTransient<IToken, Token>();
services.AddTransient<ISpacesApi, SpacesApi>();
services.AddTransient<IPermissionsApi, PermissionsApi>();
services.AddTransient<IRevisionsApi, RevisionsApi>();
services.AddTransient<IMediaApi, MediaApi>();
services.AddTransient<IMediaFolderApi, MediaFolderApi>();
services.AddTransient<IMediaUploadApi, MediaUploadApi>();