start link providers + linkpicker
This commit is contained in:
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace zero.Core.Entities
|
||||
{
|
||||
public class Link
|
||||
public class Link : ILink
|
||||
{
|
||||
public string ProviderAlias { get; set; }
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace zero.Core.Entities
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public Dictionary<string, string> Values { get; set; } = new Dictionary<string, string>();
|
||||
public Dictionary<string, object> Values { get; set; } = new();
|
||||
}
|
||||
|
||||
public enum LinkTarget
|
||||
@@ -24,4 +24,20 @@ namespace zero.Core.Entities
|
||||
Self = 1,
|
||||
Blank = 2
|
||||
}
|
||||
|
||||
|
||||
public interface ILink
|
||||
{
|
||||
string ProviderAlias { get; set; }
|
||||
|
||||
LinkTarget Target { get; set; }
|
||||
|
||||
string UrlSuffix { get; set; }
|
||||
|
||||
string Label { get; set; }
|
||||
|
||||
string Title { get; set; }
|
||||
|
||||
Dictionary<string, object> Values { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Linq;
|
||||
//using System.Text;
|
||||
//using System.Threading.Tasks;
|
||||
|
||||
//namespace zero.Core.Entities
|
||||
//{
|
||||
// /// <inheritdoc />
|
||||
// public class LinkArea : ILinkArea
|
||||
// {
|
||||
// /// <inheritdoc />
|
||||
// public string Alias { get; set; }
|
||||
|
||||
// /// <inheritdoc />
|
||||
// public string Name { get; set; }
|
||||
|
||||
// /// <inheritdoc />
|
||||
// public string Icon { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// HEX color (#aabbcc or #abc). Defaults to a neutral color
|
||||
// /// </summary>
|
||||
// public string Color { get; set; }
|
||||
|
||||
// /// <inheritdoc />
|
||||
// public IList<IChildSection> Children { get; } = new List<IChildSection>();
|
||||
|
||||
|
||||
// public LinkArea() { }
|
||||
|
||||
// public LinkArea(string alias, string name, string icon = null, string color = null)
|
||||
// {
|
||||
// Alias = alias;
|
||||
// Name = name;
|
||||
// Icon = icon;
|
||||
// Color = color;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// A section is a main part of the backoffice application
|
||||
// /// </summary>
|
||||
// public interface ILinkArea
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// The section alias which acts as the url slug for navigation
|
||||
// /// </summary>
|
||||
// string Alias { get; }
|
||||
|
||||
// /// <summary>
|
||||
// /// The name of the section (either a string or a translation key with @ prefix)
|
||||
// /// </summary>
|
||||
// string Name { get; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Icon of the section
|
||||
// /// </summary>
|
||||
// string Icon { get; }
|
||||
|
||||
// /// <summary>
|
||||
// /// HEX color (#aabbcc or #abc)
|
||||
// /// </summary>
|
||||
// string Color { get; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Children are displayed as a sub-navigation in the main nav area
|
||||
// /// </summary>
|
||||
// IList<IChildSection> Children { get; }
|
||||
// }
|
||||
//}
|
||||
@@ -9,7 +9,7 @@ namespace zero.Core.Options
|
||||
{
|
||||
SupportedLanguages = new string[2] { "en-US", "de-DE" };
|
||||
DefaultLanguage = SupportedLanguages[0];
|
||||
TokenExpiration = 60;
|
||||
TokenExpiration = 60 * 3;
|
||||
BackofficePath = "/zero";
|
||||
ExcludedPaths = new() { };
|
||||
Raven = new()
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Entities;
|
||||
|
||||
namespace zero.Core.Routing
|
||||
{
|
||||
public interface ILinkProvider
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
string Alias { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Task<string> ResolveLink(ILink link);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Raven.Client.Documents.Session;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Database;
|
||||
using zero.Core.Entities;
|
||||
|
||||
namespace zero.Core.Routing
|
||||
{
|
||||
public class PageLinkProvider : ILinkProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name { get; } = "@links.providers.page";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Alias { get; } = "zero.pages";
|
||||
|
||||
protected IZeroStore Store { get; set; }
|
||||
|
||||
protected IRoutes Routes { get; set; }
|
||||
|
||||
|
||||
public PageLinkProvider(IZeroStore store, IRoutes routes)
|
||||
{
|
||||
Store = store;
|
||||
Routes = routes;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> ResolveLink(ILink link)
|
||||
{
|
||||
if (!link.Values.TryGetValue("pageId", out object pageIdObj))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string pageId = pageIdObj.ToString();
|
||||
|
||||
using IAsyncDocumentSession session = Store.OpenAsyncSession();
|
||||
IPage page = await session.LoadAsync<IPage>(pageId);
|
||||
IRoute route = await Routes.GetRoute(page);
|
||||
|
||||
return route.Url;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="ui-linkpicker" :class="{'is-disabled': disabled }">
|
||||
<input ref="input" type="hidden" :value="value" />
|
||||
<!--<div class="ui-pagepicker-previews" v-if="previews.length > 0">
|
||||
<div v-for="preview in previews" class="ui-pagepicker-preview">
|
||||
<ui-select-button :icon="preview.icon" :label="preview.name" :description="preview.text" :disabled="disabled" @click="pick(preview.id)" :tokens="{ id: preview.id }" />
|
||||
<ui-icon-button v-if="!disabled" @click="remove(preview.id)" icon="fth-x" title="@ui.close" />
|
||||
</div>
|
||||
</div>-->
|
||||
<ui-select-button v-if="canAdd" icon="fth-plus" :label="limit > 1 ? '@ui.add' : '@ui.select'" @click="pick()" :disabled="disabled" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import LinkpickerOverlay from './overlay.vue';
|
||||
import Overlay from 'zero/helpers/overlay.js';
|
||||
import { extend as _extend, isArray as _isArray, isEmpty as _isEmpty, clone as _clone } from 'underscore';
|
||||
|
||||
export default {
|
||||
name: 'uiPagepicker',
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Array],
|
||||
default: null
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
previews: []
|
||||
}),
|
||||
|
||||
watch: {
|
||||
value()
|
||||
{
|
||||
this.updatePreviews();
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
multiple()
|
||||
{
|
||||
return this.limit > 1;
|
||||
},
|
||||
canAdd()
|
||||
{
|
||||
let count = Array.isArray(this.value) ? this.value.length : (!this.value ? 0 : 1);
|
||||
return !this.disabled && count < this.limit;
|
||||
}
|
||||
},
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.updatePreviews();
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
onChange(value)
|
||||
{
|
||||
this.$emit('change', value);
|
||||
this.$emit('input', value);
|
||||
// TODO this does not trigger the forms dirty flag
|
||||
},
|
||||
|
||||
|
||||
updatePreviews()
|
||||
{
|
||||
this.previews = [];
|
||||
//if (!this.value || _isEmpty(this.value))
|
||||
//{
|
||||
// this.previews = [];
|
||||
// return;
|
||||
//}
|
||||
|
||||
//let ids = _isArray(this.value) ? this.value : [this.value];
|
||||
//PagesApi.getPreviews(ids).then(res =>
|
||||
//{
|
||||
// this.previews = res;
|
||||
//});
|
||||
},
|
||||
|
||||
|
||||
remove(id)
|
||||
{
|
||||
if (Array.isArray(this.value))
|
||||
{
|
||||
let index = this.value.indexOf(id);
|
||||
this.value.splice(index, 1);
|
||||
this.onChange(this.value);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.onChange(this.limit > 1 ? [] : null);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
pick(id)
|
||||
{
|
||||
if (this.disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let options = _extend({
|
||||
title: 'Select a link',
|
||||
closeLabel: '@ui.close',
|
||||
component: LinkpickerOverlay,
|
||||
display: 'editor',
|
||||
model: this.multiple ? id : this.value
|
||||
}, typeof this.options === 'object' ? this.options : {});
|
||||
|
||||
return Overlay.open(options).then(value =>
|
||||
{
|
||||
console.info('confirmed: ' + value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-linkpicker-preview
|
||||
{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.ui-icon-button
|
||||
{
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
|
||||
i
|
||||
{
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui-linkpicker-previews + .ui-select-button,
|
||||
.ui-linkpicker-preview + .ui-linkpicker-preview
|
||||
{
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<ui-overlay-editor class="ui-pagepicker-overlay">
|
||||
<template v-slot:header>
|
||||
<ui-header-bar :title="config.title" :back-button="false" :close-button="true" />
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<ui-button type="light onbg" :label="config.closeLabel" :parent="config.rootId" @click="config.hide"></ui-button>
|
||||
</template>
|
||||
|
||||
<div v-if="opened" class="ui-box ui-pagepicker-overlay-items">
|
||||
<ui-tree ref="tree" :get="getItems" :parent="config.rootId" @select="onSelect" />
|
||||
</div>
|
||||
</ui-overlay-editor>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import PageTreeApi from 'zero/api/page-tree.js'
|
||||
|
||||
export default {
|
||||
|
||||
props: {
|
||||
model: String,
|
||||
config: Object
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
opened: false
|
||||
}),
|
||||
|
||||
computed: {
|
||||
disabledIds()
|
||||
{
|
||||
return this.config.disabledIds || [];
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mounted()
|
||||
{
|
||||
setTimeout(() => this.opened = true, 300);
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
|
||||
onSelect(item)
|
||||
{
|
||||
this.config.confirm(item);
|
||||
},
|
||||
|
||||
// get tree items
|
||||
getItems(parent)
|
||||
{
|
||||
return PageTreeApi.getChildren(parent, this.model).then(res =>
|
||||
{
|
||||
res = res.filter(x => x.id !== 'recyclebin');
|
||||
|
||||
res.forEach(item =>
|
||||
{
|
||||
if (item.id === this.model)
|
||||
{
|
||||
item.isSelected = true;
|
||||
}
|
||||
if (this.disabledIds.indexOf(item.id) > -1)
|
||||
{
|
||||
item.disabled = true;
|
||||
}
|
||||
item.hasActions = false;
|
||||
});
|
||||
|
||||
return res;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-pagepicker-overlay content
|
||||
{
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.ui-box.ui-pagepicker-overlay-items
|
||||
{
|
||||
margin: 0;
|
||||
padding: 20px 0;
|
||||
|
||||
.ui-tree-item.is-selected, .ui-tree-item:hover:not(.is-disabled)
|
||||
{
|
||||
background: var(--color-bg-xxlight);
|
||||
}
|
||||
|
||||
& +.ui-box
|
||||
{
|
||||
margin-top: var(--padding);
|
||||
}
|
||||
|
||||
.ui-tree-item.is-selected
|
||||
{
|
||||
&:after
|
||||
{
|
||||
font-family: "Feather";
|
||||
content: "\e83e";
|
||||
font-size: 16px;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.ui-tree-item-text
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -15,4 +15,9 @@ plugin.addLists(lists);
|
||||
// add routes
|
||||
plugin.addRoutes(routes);
|
||||
|
||||
plugin.install = (vue, zero) =>
|
||||
{
|
||||
console.info(vue, zero);
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<!--<template>
|
||||
<input :value="url" @input="onChange($event.target.value)" type="text" class="ui-input" :disabled="disabled" />
|
||||
</template>
|
||||
|
||||
@@ -40,4 +40,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>-->
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<ui-linkpicker :config="config" :value="value" @input="$emit('input', $event)" :disabled="disabled" :limit="limit" />
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Array],
|
||||
default: null
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
config: Object
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -41,6 +41,7 @@ import uiPagepicker from './pickers/pagePicker/pagepicker.vue';
|
||||
import uiUserpicker from './pickers/userPicker/userpicker.vue';
|
||||
import uiMailtemplatepicker from './pickers/mailPicker/mailpicker.vue';
|
||||
import uiSpacepicker from './pickers/spacePicker/spacepicker.vue';
|
||||
import uiLinkpicker from './pickers/linkPicker/linkpicker.vue';
|
||||
import uiPick from './pickers/pick.vue';
|
||||
|
||||
import uiTable from './tables/table.vue';
|
||||
@@ -110,6 +111,7 @@ export default {
|
||||
uiUserpicker,
|
||||
uiMailtemplatepicker,
|
||||
uiSpacepicker,
|
||||
uiLinkpicker,
|
||||
uiPick,
|
||||
|
||||
uiTable,
|
||||
|
||||
@@ -20,6 +20,7 @@ import DateRangePicker from '../editor/fields/daterangepicker.vue';
|
||||
import IconPicker from '../editor/fields/iconPicker.vue';
|
||||
import LanguagePicker from '../editor/fields/language.vue';
|
||||
import PagePicker from '../editor/fields/pagepicker.vue';
|
||||
import LinkPicker from '../editor/fields/linkpicker.vue';
|
||||
import InputList from '../editor/fields/inputlist.vue';
|
||||
import Media from '../editor/fields/media.vue';
|
||||
import Modules from '../editor/fields/modules.vue';
|
||||
@@ -215,16 +216,6 @@ class EditorField
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render a link picker
|
||||
* @returns {EditorField}
|
||||
*/
|
||||
linkPicker()
|
||||
{
|
||||
return this._setComponent(Link, {});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {object} EditorSelectItem
|
||||
* @param {object} key - Key/Id of the item
|
||||
@@ -416,6 +407,18 @@ class EditorField
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render a link picker
|
||||
* @param {object} [options] - Custom options
|
||||
* @param {number} [options.limit=1] - Limit of selection
|
||||
* @returns {EditorField}
|
||||
*/
|
||||
linkPicker(options)
|
||||
{
|
||||
return this._setComponent(LinkPicker, { ...options });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a list of strings
|
||||
* @param {number} [limit=10] - Limit the inputs
|
||||
|
||||
@@ -130,6 +130,13 @@
|
||||
"raw": "Edit HTML"
|
||||
},
|
||||
|
||||
"links": {
|
||||
"providers": {
|
||||
"page": "Pages",
|
||||
"media": "Media"
|
||||
}
|
||||
},
|
||||
|
||||
"errors": {
|
||||
"idnotfound": "Could not find an entity for the given ID",
|
||||
"onsave": {
|
||||
|
||||
Reference in New Issue
Block a user