spaces work now!
This commit is contained in:
+25
-108
@@ -1,6 +1,4 @@
|
||||
using FluentValidation.Results;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Raven.Client.Documents;
|
||||
using Raven.Client.Documents;
|
||||
using Raven.Client.Documents.Linq;
|
||||
using Raven.Client.Documents.Session;
|
||||
using System;
|
||||
@@ -9,90 +7,60 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Extensions;
|
||||
using zero.Core.Options;
|
||||
using zero.Core.Plugins;
|
||||
|
||||
namespace zero.Core.Api
|
||||
{
|
||||
public class SpacesApi : ISpacesApi
|
||||
public class SpacesApi : AppAwareBackofficeApi, ISpacesApi
|
||||
{
|
||||
protected IDocumentStore Raven { get; private set; }
|
||||
|
||||
protected IPermissionsApi PermissionsApi { get; private set; }
|
||||
|
||||
protected IZeroOptions Options { get; set; }
|
||||
|
||||
|
||||
public SpacesApi(IDocumentStore raven, IPermissionsApi permissionsApi, IZeroOptions options)
|
||||
public SpacesApi(IBackofficeStore store, IPermissionsApi permissionsApi) : base(store)
|
||||
{
|
||||
Raven = raven;
|
||||
Scope.IncludeShared = true;
|
||||
PermissionsApi = permissionsApi;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public Space GetByAlias(string alias)
|
||||
{
|
||||
return Options.Spaces.GetAllItems().FirstOrDefault(x => x.Alias.Equals(alias, StringComparison.InvariantCultureIgnoreCase));
|
||||
return GetAll().FirstOrDefault(x => x.Alias.Equals(alias, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<Space> GetAll()
|
||||
{
|
||||
return Options.Spaces.GetAllItems();
|
||||
return Backoffice.Options.Spaces.GetAllItems();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<T> GetItem<T>(string alias) where T : SpaceContent
|
||||
public async Task<ISpaceContent> GetItem(string alias, string id = null)
|
||||
{
|
||||
Space space = GetByAlias(alias);
|
||||
|
||||
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
|
||||
{
|
||||
return await session.Query<SpaceContent>()
|
||||
.Where(x => x.SpaceAlias == alias)
|
||||
.ProjectInto<T>()
|
||||
return await session.Query<ISpaceContent>()
|
||||
.Scope(Scope)
|
||||
.Where(x => x.SpaceAlias == space.Alias)
|
||||
.WhereIf(x => x.Id == id, !id.IsNullOrEmpty())
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<T> GetItem<T>(string alias, string id) where T : SpaceContent
|
||||
{
|
||||
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
|
||||
{
|
||||
return await session.Query<SpaceContent>()
|
||||
.Where(x => x.SpaceAlias == alias && x.Id == id)
|
||||
.ProjectInto<T>()
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IList<T>> GetList<T>(string alias) where T : SpaceContent
|
||||
{
|
||||
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
|
||||
{
|
||||
return await session.Query<SpaceContent>()
|
||||
.Where(x => x.SpaceAlias == alias)
|
||||
.ProjectInto<T>()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ListResult<T>> GetListByQuery<T>(string alias, ListQuery<T> query, string appId = null) where T : SpaceContent
|
||||
public async Task<ListResult<ISpaceContent>> GetListByQuery(string alias, ListQuery<ISpaceContent> query)
|
||||
{
|
||||
query.SearchSelector = item => item.Name;
|
||||
|
||||
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
|
||||
{
|
||||
return await session.Query<T>()
|
||||
.Scope(appId)
|
||||
return await session.Query<ISpaceContent>()
|
||||
.Scope(Scope)
|
||||
.Where(x => x.SpaceAlias == alias)
|
||||
.ToQueriedListAsync(query);
|
||||
}
|
||||
@@ -100,58 +68,17 @@ namespace zero.Core.Api
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<EntityResult<T>> Save<T>(string alias, T model) where T : SpaceContent
|
||||
public async Task<EntityResult<ISpaceContent>> Save(string alias, ISpaceContent model)
|
||||
{
|
||||
Space space = GetByAlias(alias);
|
||||
//RendererConfig config = GetEditorConfig(alias);
|
||||
|
||||
//if (config.Validator != null)
|
||||
//{
|
||||
// ValidationResult validation = await config.Validator.ValidateAsync(model);
|
||||
|
||||
// if (!validation.IsValid)
|
||||
// {
|
||||
// return EntityResult<T>.Fail(validation);
|
||||
// }
|
||||
//}
|
||||
|
||||
if (model.Id.IsNullOrEmpty())
|
||||
{
|
||||
model.AppId = "zero.applications.1-A"; // TODO real app id
|
||||
model.CreatedDate = DateTimeOffset.Now;
|
||||
}
|
||||
|
||||
model.SpaceAlias = alias;
|
||||
model.Alias = Safenames.Alias(model.Name);
|
||||
|
||||
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
|
||||
{
|
||||
await session.StoreAsync(model);
|
||||
await session.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return EntityResult<T>.Success(model);
|
||||
model.SpaceAlias = GetByAlias(alias)?.Alias;
|
||||
return await SaveModel(model, null);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<EntityResult<object>> Delete(string alias, string id)
|
||||
public async Task<EntityResult<ISpaceContent>> Delete(string alias, string id)
|
||||
{
|
||||
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
|
||||
{
|
||||
SpaceContent spaceContent = await session.LoadAsync<SpaceContent>(id);
|
||||
|
||||
if (spaceContent == null || !spaceContent.SpaceAlias.Equals(alias, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return EntityResult.Fail("@errors.ondelete.idnotfound");
|
||||
}
|
||||
|
||||
session.Delete(spaceContent);
|
||||
|
||||
await session.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return EntityResult.Success();
|
||||
return await DeleteById<ISpaceContent>(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,31 +98,21 @@ namespace zero.Core.Api
|
||||
/// <summary>
|
||||
/// Get editor item for a space
|
||||
/// </summary>
|
||||
Task<T> GetItem<T>(string alias) where T : SpaceContent;
|
||||
|
||||
/// <summary>
|
||||
/// Get editor item for a space
|
||||
/// </summary>
|
||||
Task<T> GetItem<T>(string alias, string id) where T : SpaceContent;
|
||||
|
||||
/// <summary>
|
||||
/// Get all list items by space alias
|
||||
/// </summary>
|
||||
Task<IList<T>> GetList<T>(string alias) where T : SpaceContent;
|
||||
Task<ISpaceContent> GetItem(string alias, string id = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get all list items for a space (with query)
|
||||
/// </summary>
|
||||
Task<ListResult<T>> GetListByQuery<T>(string alias, ListQuery<T> query, string appId = null) where T : SpaceContent;
|
||||
Task<ListResult<ISpaceContent>> GetListByQuery(string alias, ListQuery<ISpaceContent> query);
|
||||
|
||||
/// <summary>
|
||||
/// Saves a content item in a space
|
||||
/// </summary>
|
||||
Task<EntityResult<T>> Save<T>(string alias, T model) where T : SpaceContent;
|
||||
Task<EntityResult<ISpaceContent>> Save(string alias, ISpaceContent model);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a space content item
|
||||
/// </summary>
|
||||
Task<EntityResult<object>> Delete(string alias, string id);
|
||||
Task<EntityResult<ISpaceContent>> Delete(string alias, string id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,31 @@
|
||||
namespace zero.Core.Entities
|
||||
using zero.Core.Attributes;
|
||||
|
||||
namespace zero.Core.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// A list item can consist of unlimited properties and be rendered as you wish
|
||||
/// The backoffice rendering is done by an IRenderer
|
||||
/// </summary>
|
||||
public class SpaceContent : ZeroEntity, IAppAwareEntity, IZeroDbConventions
|
||||
public class SpaceContent : ZeroEntity, ISpaceContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Associated space
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public string SpaceAlias { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string AppId { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A list item can consist of unlimited properties and be rendered as you wish
|
||||
/// The backoffice rendering is done by an IRenderer
|
||||
/// </summary>
|
||||
[Collection("SpaceContents")]
|
||||
public interface ISpaceContent : IZeroEntity, IAppAwareEntity, IZeroDbConventions
|
||||
{
|
||||
/// <summary>
|
||||
/// Associated space
|
||||
/// </summary>
|
||||
string SpaceAlias { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
export default {
|
||||
alias: 'space.social',
|
||||
|
||||
fields: [
|
||||
{
|
||||
field: 'twitter',
|
||||
display: 'text',
|
||||
label: 'Twitter'
|
||||
},
|
||||
{
|
||||
field: 'facebook',
|
||||
display: 'text',
|
||||
label: 'Facebook'
|
||||
},
|
||||
{
|
||||
field: 'youtube',
|
||||
display: 'text',
|
||||
label: 'YouTube'
|
||||
},
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
export default {
|
||||
alias: 'space.team',
|
||||
|
||||
fields: [
|
||||
{
|
||||
field: 'name',
|
||||
display: 'text',
|
||||
label: 'Name',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
field: 'position',
|
||||
display: 'text',
|
||||
label: 'Position'
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
display: 'text',
|
||||
label: 'Email',
|
||||
required: true
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -27,7 +27,7 @@
|
||||
default: () => [ 'uiProperty' ]
|
||||
},
|
||||
route: {
|
||||
type: String,
|
||||
type: [String, Object],
|
||||
default: null
|
||||
}
|
||||
},
|
||||
@@ -173,10 +173,11 @@
|
||||
|
||||
if (response.model && this.route && this.$route.name !== this.route)
|
||||
{
|
||||
this.$router.replace({
|
||||
name: this.route,
|
||||
params: { id: response.model.id }
|
||||
});
|
||||
let routeObj = typeof this.route === 'object' ? this.route : { name: this.route };
|
||||
routeObj.params = routeObj.params || {};
|
||||
routeObj.params.id = response.model.id;
|
||||
|
||||
this.$router.replace(routeObj);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</slot>
|
||||
<slot name="settings-properties"></slot>
|
||||
</div>
|
||||
<div class="ui-box">
|
||||
<div class="ui-box" v-if="value.id">
|
||||
<slot name="infos">
|
||||
<ui-property v-if="value.id" label="@ui.id" :is-text="true">
|
||||
{{value.id}}
|
||||
|
||||
@@ -20,6 +20,16 @@ if (section)
|
||||
component: () => import('zero/pages/' + alias + '/spaces')
|
||||
});
|
||||
|
||||
routes.push({
|
||||
path: ':alias/create',
|
||||
props: true,
|
||||
name: 'space-create',
|
||||
component: () => import('zero/pages/' + alias + '/spaces'),
|
||||
meta: {
|
||||
create: true
|
||||
}
|
||||
});
|
||||
|
||||
//routes.push({
|
||||
// path: 'list/:id',
|
||||
// props: true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="spaces">
|
||||
<div class="app-tree spaces-tree" v-resizable="resizable">
|
||||
<ui-header-bar title="Spaces" />
|
||||
<ui-header-bar title="@space.list" />
|
||||
<div class="spaces-tree-items">
|
||||
<div v-for="item in spaces" class="spaces-tree-item" :class="getClasses(item)">
|
||||
<router-link :to="{ name: 'space', params: { alias: item.alias } }" class="spaces-tree-item-link">
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
this.space = _find(this.spaces, space => space.alias === this.$route.params.alias);
|
||||
|
||||
if (this.space.view === 'editor' || this.$route.params.id)
|
||||
if (this.space.view === 'editor' || this.$route.params.id || this.$route.meta.create)
|
||||
{
|
||||
this.component = SpaceEditor;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
<template>
|
||||
<ui-form ref="form" class="space-editor" v-slot="form" @submit="onSubmit" @load="onLoad">
|
||||
<ui-header-bar :back-button="isList" :title="title" title-empty="Space">
|
||||
<ui-dropdown v-if="isList && !disabled" align="right">
|
||||
<template v-slot:button>
|
||||
<ui-button type="white" label="@ui.actions" caret="down" />
|
||||
</template>
|
||||
<ui-dropdown-separator />
|
||||
</ui-dropdown>
|
||||
<ui-button :submit="true" label="@ui.save" :state="form.state" v-if="!disabled" />
|
||||
</ui-header-bar>
|
||||
<ui-editor v-if="renderer" :config="renderer" v-model="model" />
|
||||
<ui-form ref="form" class="space-editor" v-slot="form" @submit="onSubmit" @load="onLoad" :route="route">
|
||||
<ui-form-header v-model="model" title="@space.name" :disabled="disabled" :is-create="!id" :state="form.state" :can-delete="meta.canDelete" @delete="onDelete" />
|
||||
<ui-editor v-if="renderer" :config="renderer" v-model="model" :meta="meta" />
|
||||
</ui-form>
|
||||
</template>
|
||||
|
||||
@@ -26,15 +18,24 @@
|
||||
|
||||
data: () => ({
|
||||
disabled: false,
|
||||
renderer: {},
|
||||
model: null,
|
||||
fullModel: null
|
||||
renderer: null,
|
||||
meta: {},
|
||||
route: null, //zero.alias.sections.settings + '-' + zero.alias.settings.countries + '-edit',
|
||||
model: { name: null }
|
||||
}),
|
||||
|
||||
computed: {
|
||||
id()
|
||||
{
|
||||
return this.$route.params.id;
|
||||
},
|
||||
alias()
|
||||
{
|
||||
return this.$route.params.alias;
|
||||
},
|
||||
isList()
|
||||
{
|
||||
return !!this.$route.params.id;
|
||||
return !!this.id;
|
||||
},
|
||||
title()
|
||||
{
|
||||
@@ -42,59 +43,33 @@
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteLeave(to, from, next)
|
||||
{
|
||||
this.$refs.form.beforeRouteLeave(to, from, next);
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
onLoad(form)
|
||||
{
|
||||
form.load(SpacesApi.getContent(this.$route.params.alias, this.$route.params.id)).then(response =>
|
||||
form.load(SpacesApi.getContent(this.alias, this.id)).then(response =>
|
||||
{
|
||||
this.renderer = response.config;
|
||||
this.model = response.model;
|
||||
this.fullModel = response;
|
||||
this.disabled = !response.meta.canEdit;
|
||||
this.renderer = 'space.' + response.entity.spaceAlias;
|
||||
this.meta = response.meta;
|
||||
this.model = response.entity;
|
||||
this.route = { name: 'space-item', params: { alias: this.alias } };
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
onSubmit(form)
|
||||
{
|
||||
this.fullModel.model = this.model;
|
||||
|
||||
form.handle(SpacesApi.save(this.fullModel)).then(response =>
|
||||
{
|
||||
console.info(response);
|
||||
});
|
||||
form.handle(SpacesApi.save(this.model));
|
||||
},
|
||||
|
||||
|
||||
onDelete(item, opts)
|
||||
{
|
||||
opts.hide();
|
||||
|
||||
Overlay.confirmDelete().then((opts) =>
|
||||
{
|
||||
opts.state('loading');
|
||||
|
||||
SpacesApi.delete(this.$route.params.alias, this.$route.params.id).then(response =>
|
||||
{
|
||||
if (response.success)
|
||||
{
|
||||
opts.state('success');
|
||||
opts.hide();
|
||||
this.$router.go(-1);
|
||||
// TODO show message
|
||||
}
|
||||
else
|
||||
{
|
||||
opts.errors(response.errors);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
this.$refs.form.onDelete(SpacesApi.delete.bind(this, this.alias, this.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="list">
|
||||
<ui-header-bar :title="space.name" title-empty="List">
|
||||
<ui-table-filter v-model="tableConfig" />
|
||||
<ui-button label="Add item" icon="fth-plus" />
|
||||
<ui-button label="@ui.add" icon="fth-plus" @click="add" />
|
||||
</ui-header-bar>
|
||||
<div class="ui-blank-box">
|
||||
<ui-table v-model="tableConfig" />
|
||||
@@ -61,8 +61,16 @@
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
add()
|
||||
{
|
||||
this.$router.push({
|
||||
name: 'space-create',
|
||||
params: { alias: this.space.alias }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -102,8 +102,8 @@
|
||||
:root
|
||||
{
|
||||
// accent colors
|
||||
--color-primary: #fdd330; // #fdd330; //linear-gradient(75deg, #48da9c, #3eb8ad); //#484c5b; // #292b2c; // #00aea2
|
||||
--color-primary-fg: #191e25;
|
||||
--color-primary: #18a471; // #fdd330; //linear-gradient(75deg, #48da9c, #3eb8ad); //#484c5b; // #292b2c; // #00aea2
|
||||
--color-primary-fg: #fff;
|
||||
--color-primary-low: rgba(229, 225, 221, 0.3);
|
||||
--color-primary-two: #e5e4df;
|
||||
--color-primary-line: #e0dfda;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Api;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Extensions;
|
||||
using zero.Core.Identity;
|
||||
using zero.Web.Models;
|
||||
|
||||
namespace zero.Web.Controllers
|
||||
{
|
||||
@@ -14,12 +14,10 @@ namespace zero.Web.Controllers
|
||||
public class SpacesController : BackofficeController
|
||||
{
|
||||
ISpacesApi Api;
|
||||
IAuthenticationApi AuthenticationApi;
|
||||
|
||||
public SpacesController(ISpacesApi api, IAuthenticationApi authenticationApi)
|
||||
public SpacesController(ISpacesApi api)
|
||||
{
|
||||
Api = api;
|
||||
AuthenticationApi = authenticationApi;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +48,7 @@ namespace zero.Web.Controllers
|
||||
/// <summary>
|
||||
/// Get list items in a space
|
||||
/// </summary>
|
||||
public async Task<IActionResult> GetList([FromQuery] string alias, [FromQuery] ListQuery<SpaceContent> query = null)
|
||||
public async Task<IActionResult> GetList([FromQuery] string alias, [FromQuery] ListQuery<ISpaceContent> query = null)
|
||||
{
|
||||
if (!CanReadSpace(alias))
|
||||
{
|
||||
@@ -64,44 +62,46 @@ namespace zero.Web.Controllers
|
||||
/// <summary>
|
||||
/// Get list items in a space
|
||||
/// </summary>
|
||||
public IActionResult GetContent([FromQuery] string alias, [FromQuery] string contentId = null)
|
||||
public async Task<IActionResult> GetContent([FromQuery] string alias, [FromQuery] string contentId = null)
|
||||
{
|
||||
if (!CanReadSpace(alias))
|
||||
{
|
||||
return new StatusCodeResult(403);
|
||||
}
|
||||
|
||||
//TeamMember model = new TeamMember();
|
||||
Space space = Api.GetByAlias(alias);
|
||||
ISpaceContent model;
|
||||
|
||||
//if (!contentId.IsNullOrEmpty())
|
||||
//{
|
||||
// model = await Api.GetItem<TeamMember>(alias, contentId);
|
||||
//}
|
||||
|
||||
JsonSerializerSettings settings = JsonConvert.DefaultSettings();
|
||||
settings.TypeNameHandling = TypeNameHandling.Objects;
|
||||
|
||||
return Json(new SpaceContentEditModel()
|
||||
if (space == null)
|
||||
{
|
||||
//Id = model.Id,
|
||||
Alias = alias,
|
||||
//Model = model,
|
||||
//Config = Api.GetEditorConfig(alias)
|
||||
}, settings);
|
||||
return new StatusCodeResult(404);
|
||||
}
|
||||
|
||||
if (space.View != SpaceView.List || !contentId.IsNullOrEmpty())
|
||||
{
|
||||
model = await Api.GetItem(alias, contentId);
|
||||
}
|
||||
else
|
||||
{
|
||||
model = Activator.CreateInstance(space.Type) as SpaceContent;
|
||||
model.SpaceAlias = space.Alias;
|
||||
}
|
||||
|
||||
return Edit(model);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Save content item
|
||||
/// </summary>
|
||||
public async Task<IActionResult> Save([FromBody] SpaceContentEditModel model)
|
||||
public async Task<IActionResult> Save([FromBody] ISpaceContent model)
|
||||
{
|
||||
if (!CanWriteSpace(model.Alias))
|
||||
if (!CanWriteSpace(model.SpaceAlias))
|
||||
{
|
||||
return new StatusCodeResult(403);
|
||||
}
|
||||
|
||||
return Json(await Api.Save(model.Alias, model.Model));
|
||||
return Json(await Api.Save(model.SpaceAlias, model));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -493,6 +493,11 @@
|
||||
}
|
||||
},
|
||||
|
||||
"space": {
|
||||
"list": "Spaces",
|
||||
"name": "Space"
|
||||
},
|
||||
|
||||
"_test": {
|
||||
"fields": {
|
||||
"name": "Name",
|
||||
|
||||
Reference in New Issue
Block a user