backend urls
This commit is contained in:
@@ -16,6 +16,9 @@ public abstract class ZeroApiController : ControllerBase
|
||||
IZeroMapper _mapper;
|
||||
protected IZeroMapper Mapper => _mapper ?? (_mapper = HttpContext?.RequestServices?.GetService<IZeroMapper>());
|
||||
|
||||
IZeroContext _context;
|
||||
protected IZeroContext ZeroContext => _context ?? (_context = HttpContext?.RequestServices?.GetService<IZeroContext>());
|
||||
|
||||
|
||||
public ApiRequestHints Hints { get; protected set; } = new();
|
||||
}
|
||||
|
||||
@@ -64,6 +64,21 @@ public class PagesController : ZeroApiTreeEntityStoreController<Page, IPagesStor
|
||||
return GetModelsByIndex<zero_Api_Pages_Listing>(query);
|
||||
}
|
||||
|
||||
[HttpGet("{id}/urls")]
|
||||
[ZeroAuthorize(PagePermissions.Read)]
|
||||
public async Task<ActionResult<UrlsResult>> GetUrls(string id)
|
||||
{
|
||||
string url = await Routes.GetUrl<Page>(id);
|
||||
return new UrlsResult(ZeroContext.Application.Domains.FirstOrDefault(), url);
|
||||
}
|
||||
|
||||
[HttpGet("{id}/urls/preview")]
|
||||
[ZeroAuthorize(PagePermissions.Read)]
|
||||
public async Task<ActionResult<PreviewUrlResult>> GetPreviewUrl(string id)
|
||||
{
|
||||
return new PreviewUrlResult(await Routes.GetUrl<Page>(id));
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
[ZeroAuthorize(PagePermissions.Create)]
|
||||
public virtual Task<ActionResult<Result>> Create(Page saveModel) => CreateModel(saveModel);
|
||||
|
||||
@@ -240,12 +240,12 @@ export function compileEditor(zero: Zero, editor: ZeroEditor): ZeroCompiledEdito
|
||||
name: editorTab.name,
|
||||
sort: editorTab.sort,
|
||||
alias: editorTab.alias,
|
||||
class: null,
|
||||
class: editorTab.class,
|
||||
count: editorTab.count,
|
||||
disabled: editorTab.disabled,
|
||||
hidden: editorTab.hidden,
|
||||
fieldsets: [],
|
||||
component: null
|
||||
component: editorTab.component
|
||||
} as ZeroCompiledEditorTab;
|
||||
|
||||
let fieldsets = [];
|
||||
@@ -316,7 +316,7 @@ export function compileEditor(zero: Zero, editor: ZeroEditor): ZeroCompiledEdito
|
||||
});
|
||||
|
||||
|
||||
if (tab.fieldsets.length)
|
||||
if (tab.fieldsets.length || tab.component != null)
|
||||
{
|
||||
model.tabs.push(tab);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ZeroEditorField } from "zero/schemas";
|
||||
import { ZeroEditorFieldImpl, ZeroEditorFieldConfiguration, createFieldProxy } from "./editor-field";
|
||||
import { arrayRemove } from '../utils';
|
||||
import { Component } from "vue";
|
||||
|
||||
|
||||
export class ZeroEditorCanvasBase
|
||||
@@ -86,6 +87,8 @@ export class ZeroEditorTab extends ZeroEditorCanvasBaseWithFieldset
|
||||
count: (model: any) => number | undefined = () => undefined;
|
||||
hidden: (model: any) => boolean = () => false;
|
||||
disabled: (model: any) => boolean = () => false;
|
||||
class: string | null = null;
|
||||
component: Component | null = null;
|
||||
|
||||
constructor(alias: string, name: string)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ export default function (app: App)
|
||||
{
|
||||
app.component('ui-editor', defineAsyncComponent(() => import('./ui-editor.vue')));
|
||||
app.component('ui-editor-component', defineAsyncComponent(() => import('./ui-editor-component.vue')));
|
||||
app.component('ui-editor-infos', defineAsyncComponent(() => import('./ui-editor-infos.vue')));
|
||||
app.component('ui-editor-meta', defineAsyncComponent(() => import('./ui-editor-meta.vue')));
|
||||
app.component('ui-editor-header', defineAsyncComponent(() => import('./ui-editor-header.vue')));
|
||||
app.component('ui-editor-page', defineAsyncComponent(() => import('./ui-editor-page.vue')));
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<div v-if="visible" class="editor-infos">
|
||||
<div class="ui-box is-light editor-infos-aside">
|
||||
<slot name="before"></slot>
|
||||
<template v-if="modelValue && modelValue.id">
|
||||
<ui-property v-if="modelValue.lastModifiedDate" field="lastModifiedDate" label="@ui.modifiedDate">
|
||||
<ui-date v-model="modelValue.lastModifiedDate" />
|
||||
</ui-property>
|
||||
<ui-property label="@ui.createdDate" field="createdDate">
|
||||
<ui-date v-model="modelValue.createdDate" />
|
||||
</ui-property>
|
||||
<ui-property label="@ui.entityfields.alias" field="alias">
|
||||
{{modelValue.alias}}
|
||||
</ui-property>
|
||||
<ui-property label="@ui.entityfields.sort" field="sort">
|
||||
{{modelValue.sort}}
|
||||
</ui-property>
|
||||
<ui-property v-if="modelValue.key" label="@ui.entityfields.key" field="key">
|
||||
{{modelValue.key}}
|
||||
</ui-property>
|
||||
</template>
|
||||
<slot name="after"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'uiEditorInfos',
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [Object, Array]
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
visible()
|
||||
{
|
||||
return (this.modelValue && this.modelValue.id) || this.$slots.hasOwnProperty('before') || this.$slots.hasOwnProperty('after');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="editor-meta">
|
||||
<div class="ui-box">
|
||||
<ui-property v-if="!isCreate && urls.length" label="@page.infotab.links" :is-text="true" :vertical="false">
|
||||
<a v-for="url in urls" class="ui-linktext" :href="url" target="_blank"><ui-icon symbol="fth-external-link"></ui-icon> {{url}}</a>
|
||||
</ui-property>
|
||||
<!--<ui-property label="@page.schedule.label" :is-text="true" :vertical="false">
|
||||
<ui-daterangepicker :value="{ from: value.publishDate, to: value.unpublishDate }" @input="onRangeChange" :class="{ 'is-primary': value.publishDate || value.unpublishDate }" :disabled="disabled" />
|
||||
</ui-property>-->
|
||||
<ui-property label="@page.type" :is-text="true" v-if="pageType" :vertical="false">
|
||||
<ui-icon :symbol="pageType.icon" style="margin-right:6px;"></ui-icon>{{pageType.name}}
|
||||
</ui-property>
|
||||
<ui-property v-if="!isCreate" label="@ui.id" :is-text="true" :vertical="false">
|
||||
{{value.id}}
|
||||
</ui-property>
|
||||
<ui-property v-if="!isCreate" label="@ui.createdDate" :is-text="true" :vertical="false">
|
||||
<ui-date v-model="value.createdDate" :split="true" format="long" />
|
||||
</ui-property>
|
||||
<ui-property v-if="!isCreate" label="@ui.modifiedDate" :is-text="true" :vertical="false">
|
||||
<ui-date v-model="value.lastModifiedDate" :split="true" format="long" />
|
||||
</ui-property>
|
||||
<ui-property v-if="!isCreate" label="@ui.entityfields.alias" :is-text="true" :vertical="false">
|
||||
{{value.alias}}
|
||||
</ui-property>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import api from '../api';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: [ Object, Array ]
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
pageType: null,
|
||||
urls: [],
|
||||
urlDomain: null
|
||||
}),
|
||||
|
||||
computed: {
|
||||
isCreate()
|
||||
{
|
||||
return !this.value.id;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
'value.lastModifiedDate'()
|
||||
{
|
||||
this.reloadUrls();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mounted()
|
||||
{
|
||||
//api.getPageType(this.value.pageTypeAlias).then(pageType =>
|
||||
//{
|
||||
// this.pageType = pageType;
|
||||
//});
|
||||
this.reloadUrls();
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
|
||||
async reloadUrls()
|
||||
{
|
||||
if (this.value.id)
|
||||
{
|
||||
const result = await api.getUrls(this.value.id);
|
||||
this.urls = result.data.urls;
|
||||
this.urlDomain = result.data.domain;
|
||||
}
|
||||
},
|
||||
|
||||
getRevisions(page)
|
||||
{
|
||||
//return api.getRevisions(this.value.id, page);
|
||||
},
|
||||
|
||||
onRangeChange(value)
|
||||
{
|
||||
this.value.publishDate = value.from;
|
||||
this.value.unpublishDate = value.to;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.editor-meta
|
||||
{
|
||||
padding: 0 !important;
|
||||
}
|
||||
.editor-meta .ui-view-box-aside
|
||||
{
|
||||
padding: 0;
|
||||
}
|
||||
.editor-meta .ui-box
|
||||
{
|
||||
margin: 0;
|
||||
}
|
||||
.editor-meta .ui-box + .ui-box
|
||||
{
|
||||
margin-top: var(--padding-s);
|
||||
}
|
||||
.editor-meta .ui-property + .ui-property
|
||||
{
|
||||
/*border-top: none;
|
||||
margin-top: 0;
|
||||
padding-top: var(--padding-s);*/
|
||||
}
|
||||
.editor-meta .ui-box:last-child
|
||||
{
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -102,7 +102,8 @@
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
display: flex;
|
||||
gap: var(--padding-l);
|
||||
flex-direction: column;
|
||||
gap: var(--padding-xs);
|
||||
}
|
||||
|
||||
.editor-infos .ui-property
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
</ui-tab>
|
||||
</ui-tabs>
|
||||
<aside class="editor-aside" v-if="asideDefined">
|
||||
<slot name="aside"></slot>
|
||||
<slot name="aside" v-bind="{ value }"></slot>
|
||||
</aside>
|
||||
<aside class="editor-below" v-if="belowDefined">
|
||||
<slot name="below"></slot>
|
||||
<slot name="below" v-bind="{ value }"></slot>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,9 @@ export default {
|
||||
|
||||
getPreviews: (ids: string[], config?: ApiRequestConfig) => get(`backoffice/pages/previews`, { ...config, params: { ids } }),
|
||||
|
||||
getUrls: (id: string, config?: ApiRequestConfig) => get(`pages/${id}/urls`, { ...config }),
|
||||
|
||||
getPreviewUrl: (id: string, config?: ApiRequestConfig) => get(`pages/${id}/urls/preview`, { ...config }),
|
||||
|
||||
create: (model: any, config?: ApiRequestConfig) => post('pages', model, config),
|
||||
|
||||
|
||||
@@ -2,19 +2,18 @@
|
||||
<ui-form :key="id" ref="form" class="page page-editor" v-slot="form" @submit="onSubmit" @load="onLoad" :route="route">
|
||||
<ui-form-header v-model:value="model" title="@page.name" :disabled="disabled" :is-create="!id" :state="form.state" :active-toggle="!isFolder" :can-delete="meta.canDelete" @delete="onDelete" :sticky="true">
|
||||
<template v-slot:actions>
|
||||
<!--<ui-dropdown-button v-if="!isFolder" label="@page.preview.title" icon="fth-eye" :disabled="disabled" @click="openPreview" />
|
||||
<ui-dropdown-separator v-if="!isFolder" />-->
|
||||
<ui-dropdown-button v-if="hasPreview" label="@page.preview.title" icon="fth-eye" :disabled="disabled" :prevent="true" @click="openPreview" />
|
||||
<ui-dropdown-separator v-if="hasPreview" />
|
||||
<ui-dropdown-button label="@ui.move.title" icon="fth-corner-down-right" @click="move(model)" />
|
||||
<ui-dropdown-button label="@ui.copy.title" icon="fth-copy" @click="copy(model)" />
|
||||
<ui-dropdown-separator />
|
||||
</template>
|
||||
<!--<template v-slot:buttons>
|
||||
<ui-button type="light onbg" icon="fth-info" />
|
||||
</template>-->
|
||||
</ui-form-header>
|
||||
|
||||
<ui-editor v-if="!loading && editor" :config="editor" v-model="model" :is-page="true" infos="aside" :meta="meta" :disabled="disabled" :scope="true" :on-configure="onEditorConfigure">
|
||||
<template v-slot:below>
|
||||
<ui-editor-infos v-model="model" :disabled="disabled" />
|
||||
</template>
|
||||
</ui-editor>
|
||||
<ui-editor v-if="!loading && editor" :config="editor" v-model="model" :is-page="true" infos="aside" :meta="meta" :disabled="disabled" :scope="true" :on-configure="onEditorConfigure" />
|
||||
|
||||
<div v-if="isFolder">
|
||||
<!-- // TODO list children -->
|
||||
@@ -59,6 +58,10 @@
|
||||
isCreate()
|
||||
{
|
||||
return this.$route.name === 'pages-create' || !this.id;
|
||||
},
|
||||
hasPreview()
|
||||
{
|
||||
return this.id && !this.isFolder;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -110,7 +113,7 @@
|
||||
|
||||
var config = { system: this.$route.query['zero.scope'] == 'system' };
|
||||
const response = await form.load(() => !this.isCreate ? api.getById(this.id, undefined, config) : api.getEmpty(this.flavor, this.parent, config));
|
||||
this.model = response;
|
||||
this.model = response;
|
||||
|
||||
if (this.model)
|
||||
{
|
||||
@@ -184,6 +187,19 @@
|
||||
disabled: _ => false,
|
||||
component: PageInfoTab
|
||||
});
|
||||
},
|
||||
|
||||
async openPreview(_, opts)
|
||||
{
|
||||
opts.loading(true);
|
||||
const result = await api.getPreviewUrl(this.id);
|
||||
opts.loading(false);
|
||||
if (result.data && result.data.url)
|
||||
{
|
||||
var resolved = this.$router.resolve({ name: 'preview', query: { path: result.data.url } });
|
||||
window.open(window.location.origin + resolved.href, 'blank');
|
||||
opts.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
<template>
|
||||
<div class="page-editor-info">
|
||||
<div class="ui-box" v-if="!isCreate && urls.length">
|
||||
<ui-property label="@page.infotab.links" :is-text="true" :vertical="false">
|
||||
<a v-for="url in urls" class="ui-link" :href="url" target="_blank"><ui-icon symbol="fth-external-link"></ui-icon> {{url}}</a>
|
||||
</ui-property>
|
||||
<!--<ui-property label="@page.infotab.revisions" :is-text="true" :vertical="false">-->
|
||||
<!--<ui-revisions :get="getRevisions" />-->
|
||||
<!--</ui-property>-->
|
||||
</div>
|
||||
<div class="ui-box">
|
||||
<ui-property v-if="!isCreate && urls.length" label="@page.infotab.links" :is-text="true" :vertical="false">
|
||||
<a v-for="url in urls" class="ui-linktext" :href="urlDomain + url" target="_blank"><ui-icon symbol="fth-external-link"></ui-icon> {{url}}</a>
|
||||
</ui-property>
|
||||
<!--<ui-property label="@page.schedule.label" :is-text="true" :vertical="false">
|
||||
<ui-daterangepicker :value="{ from: value.publishDate, to: value.unpublishDate }" @input="onRangeChange" :class="{ 'is-primary': value.publishDate || value.unpublishDate }" :disabled="disabled" />
|
||||
</ui-property>-->
|
||||
@@ -48,7 +43,8 @@
|
||||
|
||||
data: () => ({
|
||||
pageType: null,
|
||||
urls: []
|
||||
urls: [],
|
||||
urlDomain: null
|
||||
}),
|
||||
|
||||
computed: {
|
||||
@@ -59,24 +55,36 @@
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
'value.lastModifiedDate'()
|
||||
{
|
||||
this.reloadUrls();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mounted()
|
||||
{
|
||||
//api.getPageType(this.value.pageTypeAlias).then(pageType =>
|
||||
//{
|
||||
// this.pageType = pageType;
|
||||
//});
|
||||
//if (this.value.id)
|
||||
//{
|
||||
// api.getUrls(this.value.id).then(urls =>
|
||||
// {
|
||||
// this.urls = urls;
|
||||
// });
|
||||
//}
|
||||
this.reloadUrls();
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
|
||||
async reloadUrls()
|
||||
{
|
||||
if (this.value.id)
|
||||
{
|
||||
const result = await api.getUrls(this.value.id);
|
||||
this.urls = result.data.urls;
|
||||
this.urlDomain = result.data.domain;
|
||||
}
|
||||
},
|
||||
|
||||
getRevisions(page)
|
||||
{
|
||||
//return api.getRevisions(this.value.id, page);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace zero.Models;
|
||||
|
||||
public class UrlsResult
|
||||
{
|
||||
public string[] Urls { get; set; } = Array.Empty<string>();
|
||||
|
||||
public string Domain { get; set; }
|
||||
|
||||
public UrlsResult(string domain, params string[] urls)
|
||||
{
|
||||
Domain = domain;
|
||||
Urls = urls.Where(x => x.HasValue()).ToArray();
|
||||
}
|
||||
|
||||
public UrlsResult(Uri domain, params string[] urls)
|
||||
{
|
||||
Domain = domain.Scheme + "://" + domain.Authority.TrimEnd("/");
|
||||
Urls = urls.Where(x => x.HasValue()).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class PreviewUrlResult
|
||||
{
|
||||
public string Url { get; set; }
|
||||
|
||||
public PreviewUrlResult(string url)
|
||||
{
|
||||
Url = url;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user