first draft of link picker overlay
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
using Raven.Client.Documents.Session;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Entities;
|
||||
|
||||
namespace zero.Core.Routing
|
||||
{
|
||||
public interface ILinkListProvider : ILinkProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Get paged list items.
|
||||
/// <param name="session">Current document session</param>
|
||||
/// <param name="page">Current page number (one-based)</param>
|
||||
/// <param name="search">Search query</param>
|
||||
/// </summary>
|
||||
Task<IList<TreeItem>> GetLinkListItems(IAsyncDocumentSession session, int page = 1, string search = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Raven.Client.Documents.Session;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Entities;
|
||||
|
||||
namespace zero.Core.Routing
|
||||
{
|
||||
public interface ILinkTreeProvider : ILinkProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Get tree children for the current parent id.
|
||||
/// <param name="session">Current document session</param>
|
||||
/// <param name="parentId">Parent node id</param>
|
||||
/// <param name="activeId">Selected node so parents can be set to open for the tree to load correclty</param>
|
||||
/// </summary>
|
||||
Task<IList<TreeItem>> GetLinkTreeItems(IAsyncDocumentSession session, string parentId = null, string activeId = null);
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,7 @@
|
||||
props: {
|
||||
value: [String, Number, Object],
|
||||
items: [Array, Function],
|
||||
entity: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
entity: Object,
|
||||
disabled: Boolean,
|
||||
emptyOption: {
|
||||
type: Boolean,
|
||||
@@ -57,22 +54,18 @@
|
||||
methods: {
|
||||
rebuild()
|
||||
{
|
||||
let items = [];
|
||||
|
||||
if (!this.entity || !this.items)
|
||||
{
|
||||
this.options = items;
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this.items === 'function')
|
||||
if (this.entity && typeof this.items === 'function')
|
||||
{
|
||||
this.options = this.items(this.entity);
|
||||
}
|
||||
else
|
||||
else if (typeof this.items !== 'function' && this.items)
|
||||
{
|
||||
this.options = [...this.items];
|
||||
}
|
||||
else
|
||||
{
|
||||
this.options = [];
|
||||
}
|
||||
},
|
||||
|
||||
onChange(ev)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import { extend as _extend, isArray as _isArray, isEmpty as _isEmpty, clone as _clone } from 'underscore';
|
||||
|
||||
export default {
|
||||
name: 'uiPagepicker',
|
||||
name: 'uiLinkpicker',
|
||||
|
||||
props: {
|
||||
value: {
|
||||
@@ -29,6 +29,26 @@
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
title: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
label: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
target: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
suffix: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
areas: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -121,7 +141,15 @@
|
||||
closeLabel: '@ui.close',
|
||||
component: LinkpickerOverlay,
|
||||
display: 'editor',
|
||||
model: this.multiple ? id : this.value
|
||||
model: this.multiple ? id : this.value,
|
||||
options: {
|
||||
limit: this.limit,
|
||||
label: this.label,
|
||||
title: this.title,
|
||||
target: this.target,
|
||||
suffix: this.suffix,
|
||||
areas: this.areas
|
||||
}
|
||||
}, typeof this.options === 'object' ? this.options : {});
|
||||
|
||||
return Overlay.open(options).then(value =>
|
||||
|
||||
@@ -1,15 +1,45 @@
|
||||
<template>
|
||||
<ui-overlay-editor class="ui-pagepicker-overlay">
|
||||
<ui-overlay-editor class="ui-linkpicker-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>
|
||||
<ui-button type="light onbg" :label="config.closeLabel" :parent="config.rootId" @click="config.hide" />
|
||||
</template>
|
||||
|
||||
<div v-if="opened" class="ui-box ui-pagepicker-overlay-items">
|
||||
<ui-tree ref="tree" :get="getItems" :parent="config.rootId" @select="onSelect" />
|
||||
<div v-if="opened">
|
||||
<div class="ui-box ui-linkpicker-overlay-options">
|
||||
<ui-property label="Open in a new tab">
|
||||
<ui-toggle :value="link.target === 'blank'" @input="onTargetChange" />
|
||||
</ui-property>
|
||||
<template v-if="showOptions">
|
||||
<hr />
|
||||
<ui-property label="Label" :vertical="true">
|
||||
<input v-model="link.label" type="text" class="ui-input" maxlength="160" />
|
||||
</ui-property>
|
||||
<ui-property label="Title" :vertical="true">
|
||||
<input v-model="link.title" type="text" class="ui-input" maxlength="160" />
|
||||
</ui-property>
|
||||
<hr />
|
||||
<ui-button label="Hide options" @click="showOptions=false" />
|
||||
</template>
|
||||
<div v-if="!showOptions">
|
||||
<hr />
|
||||
<ui-button label="More options" @click="showOptions=true" caret="down" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui-box">
|
||||
<ui-property label="Area" :vertical="true">
|
||||
<ui-select v-model="current" :items="areaItems"></ui-select>
|
||||
</ui-property>
|
||||
<div class="ui-linkpicker-overlay-items" v-if="area">
|
||||
<ui-tree v-if="area.display === 'tree'" ref="tree" v-bind="treeConfig" :get="getTreeItems" @select="treeConfig.onSelect" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--<div v-if="opened" class="ui-box ui-linkpicker-overlay-items">
|
||||
<ui-tree ref="tree" :get="getItems" :parent="config.rootId" @select="onSelect" />
|
||||
</div>-->
|
||||
</ui-overlay-editor>
|
||||
</template>
|
||||
|
||||
@@ -25,34 +55,100 @@
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
opened: false
|
||||
opened: false,
|
||||
current: null,
|
||||
area: null,
|
||||
areas: [],
|
||||
areaItems: [],
|
||||
treeConfig: {
|
||||
parent: null,
|
||||
active: null,
|
||||
onSelect: (ev) => { console.info(ev); }
|
||||
},
|
||||
link: null,
|
||||
template: {
|
||||
area: null,
|
||||
target: 'default',
|
||||
urlSuffix: null,
|
||||
label: null,
|
||||
title: null,
|
||||
values: {}
|
||||
},
|
||||
showOptions: false
|
||||
}),
|
||||
|
||||
computed: {
|
||||
disabledIds()
|
||||
|
||||
watch: {
|
||||
current()
|
||||
{
|
||||
return this.config.disabledIds || [];
|
||||
this.reloadSelector();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.areas = this.zero.config.linkPicker.areas;
|
||||
this.areaItems = this.areas.map(x =>
|
||||
{
|
||||
return {
|
||||
key: x.alias,
|
||||
value: x.name
|
||||
};
|
||||
});
|
||||
this.area = this.areas[0];
|
||||
this.current = this.area.alias;
|
||||
|
||||
this.link = JSON.parse(JSON.stringify(this.template));
|
||||
this.link.area = this.current;
|
||||
|
||||
setTimeout(() => this.opened = true, 300);
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
|
||||
reloadSelector()
|
||||
{
|
||||
this.area = this.areas.find(x => x.alias === this.current);
|
||||
|
||||
if (!this.opened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (this.area.display === 'tree' && this.$refs.tree)
|
||||
{
|
||||
this.$refs.tree.refresh();
|
||||
}
|
||||
else if (this.area.display === 'list')
|
||||
{
|
||||
|
||||
}
|
||||
else if (this.area.display === 'media')
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// custom
|
||||
}
|
||||
},
|
||||
|
||||
onSelect(item)
|
||||
{
|
||||
this.config.confirm(item);
|
||||
},
|
||||
|
||||
// get tree items
|
||||
getItems(parent)
|
||||
// get list items
|
||||
getListItems(search)
|
||||
{
|
||||
return PageTreeApi.getChildren(parent, this.model).then(res =>
|
||||
|
||||
},
|
||||
|
||||
// get tree items
|
||||
getTreeItems(parent)
|
||||
{
|
||||
return PageTreeApi.getChildren(parent, null).then(res =>
|
||||
{
|
||||
res = res.filter(x => x.id !== 'recyclebin');
|
||||
|
||||
@@ -62,55 +158,73 @@
|
||||
{
|
||||
item.isSelected = true;
|
||||
}
|
||||
if (this.disabledIds.indexOf(item.id) > -1)
|
||||
{
|
||||
item.disabled = true;
|
||||
}
|
||||
item.hasActions = false;
|
||||
});
|
||||
|
||||
return res;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
onTargetChange(ev)
|
||||
{
|
||||
this.link.target = ev ? 'blank' : 'default';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-pagepicker-overlay content
|
||||
.ui-linkpicker-overlay content
|
||||
{
|
||||
padding-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.ui-box.ui-pagepicker-overlay-items
|
||||
.ui-linkpicker-overlay-options .ui-property
|
||||
{
|
||||
margin: 0;
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ui-tree-item.is-selected, .ui-tree-item:hover:not(.is-disabled)
|
||||
.ui-linkpicker-overlay-options .ui-property + .ui-property
|
||||
{
|
||||
margin-top: var(--padding-m);
|
||||
}
|
||||
|
||||
.ui-linkpicker-overlay-options .ui-property-content
|
||||
{
|
||||
display: inline;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.ui-linkpicker-overlay-options .ui-property-label
|
||||
{
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.ui-linkpicker-overlay-items > .ui-tree
|
||||
{
|
||||
margin: 24px -32px 0;
|
||||
}
|
||||
|
||||
.ui-linkpicker-overlay-items .ui-tree-item.is-selected, .ui-linkpicker-overlay-items .ui-tree-item:hover:not(.is-disabled)
|
||||
{
|
||||
background: var(--color-tree-selected);
|
||||
}
|
||||
|
||||
/*.ui-linkpicker-overlay-items .ui-tree-item.is-selected
|
||||
{
|
||||
&:after
|
||||
{
|
||||
background: var(--color-bg-xxlight);
|
||||
font-family: "Feather";
|
||||
content: "\e83e";
|
||||
font-size: 16px;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
& +.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;
|
||||
}
|
||||
.ui-tree-item-text
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
</style>
|
||||
@@ -149,7 +149,7 @@
|
||||
color: var(--color-text);
|
||||
background: var(--color-box);
|
||||
box-shadow: var(--shadow-short);
|
||||
border-top: 3px solid var(--color-primary);
|
||||
//border-top: 3px solid var(--color-primary);
|
||||
|
||||
.ui-tabs-list-item-count
|
||||
{
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
})
|
||||
.catch(error =>
|
||||
{
|
||||
console.error(error);
|
||||
this.setStatus('error', this.items, error);
|
||||
// TODO handle errors
|
||||
});
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
//import MediaApi from 'zero/api/media.js';
|
||||
//import Strings from 'zero/helpers/strings.js';
|
||||
//import Localization from 'zero/helpers/localization.js';
|
||||
//class LinkProvider
|
||||
//{
|
||||
// key;
|
||||
// label;
|
||||
// icon;
|
||||
// autoclose = true;
|
||||
// action = () =>
|
||||
// {
|
||||
// console.warn(`[zero] A list action needs a "action" callback`);
|
||||
// };
|
||||
// constructor(alias: string, name: string, type: string)
|
||||
// {
|
||||
// this.key = key;
|
||||
// this.label = label;
|
||||
// this.icon = icon;
|
||||
// this.action = action;
|
||||
// }
|
||||
// /**
|
||||
// * Calls the action
|
||||
// * @returns {ListColumn}
|
||||
// */
|
||||
// call(options)
|
||||
// {
|
||||
// console.info(options);
|
||||
// this.action(options);
|
||||
// }
|
||||
//}
|
||||
//export default ListAction;
|
||||
//# sourceMappingURL=link-provider.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"link-provider.js","sourceRoot":"","sources":["link-provider.ts"],"names":[],"mappings":"AACA,2CAA2C;AAC3C,gDAAgD;AAChD,0DAA0D;AAE1D,oBAAoB;AACpB,GAAG;AACH,QAAQ;AACR,UAAU;AACV,SAAS;AACT,qBAAqB;AACrB,kBAAkB;AAClB,KAAK;AACL,qEAAqE;AACrE,MAAM;AAEN,0DAA0D;AAC1D,KAAK;AACL,qBAAqB;AACrB,yBAAyB;AACzB,uBAAuB;AACvB,2BAA2B;AAC3B,KAAK;AAEL,OAAO;AACP,uBAAuB;AACvB,4BAA4B;AAC5B,OAAO;AACP,iBAAiB;AACjB,KAAK;AACL,4BAA4B;AAC5B,2BAA2B;AAC3B,KAAK;AACL,GAAG;AAGH,4BAA4B"}
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
//import MediaApi from 'zero/api/media.js';
|
||||
//import Strings from 'zero/helpers/strings.js';
|
||||
//import Localization from 'zero/helpers/localization.js';
|
||||
|
||||
//class LinkProvider
|
||||
//{
|
||||
// key;
|
||||
// label;
|
||||
// icon;
|
||||
// autoclose = true;
|
||||
// action = () =>
|
||||
// {
|
||||
// console.warn(`[zero] A list action needs a "action" callback`);
|
||||
// };
|
||||
|
||||
// constructor(alias: string, name: string, type: string)
|
||||
// {
|
||||
// this.key = key;
|
||||
// this.label = label;
|
||||
// this.icon = icon;
|
||||
// this.action = action;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Calls the action
|
||||
// * @returns {ListColumn}
|
||||
// */
|
||||
// call(options)
|
||||
// {
|
||||
// console.info(options);
|
||||
// this.action(options);
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
//export default ListAction;
|
||||
@@ -12,5 +12,20 @@ export default {
|
||||
images_natural: ['.jpg', '.jpeg', '.webp'],
|
||||
images_artificial: ['.png', '.webp', '.svg']
|
||||
}
|
||||
},
|
||||
|
||||
linkPicker: {
|
||||
areas: [
|
||||
{
|
||||
alias: 'zero.pages',
|
||||
name: '@zero.config.linkareas.pages',
|
||||
display: 'tree'
|
||||
},
|
||||
{
|
||||
alias: 'zero.media',
|
||||
name: '@zero.config.linkareas.media',
|
||||
display: 'media'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ui-linkpicker :config="config" :value="value" @input="$emit('input', $event)" :disabled="disabled" :limit="limit" />
|
||||
<ui-linkpicker :config="config" :value="value" @input="$emit('input', $event)" :disabled="disabled" v-bind="{ disabled, limit, title, label, target, suffix, areas }" />
|
||||
</template>
|
||||
|
||||
|
||||
@@ -18,6 +18,26 @@
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
title: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
label: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
target: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
suffix: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
areas: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
config: Object
|
||||
},
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
--padding-l: 64px;
|
||||
|
||||
--height-top: 74px;
|
||||
--radius: 3px;
|
||||
--radius: 4px;
|
||||
}
|
||||
@@ -411,6 +411,11 @@ class EditorField
|
||||
* Render a link picker
|
||||
* @param {object} [options] - Custom options
|
||||
* @param {number} [options.limit=1] - Limit of selection
|
||||
* @param {boolean} [options.title=true] - Allow input of custom link title
|
||||
* @param {boolean} [options.target=true] - Allow selection of the link target
|
||||
* @param {boolean} [options.label=false] - Allow input of a custom label for button generation
|
||||
* @param {boolean} [options.suffix=false] - Allow input of custom link URL suffix (query or hash)
|
||||
* @param {string[]} [options.areas] - Limit link areas to the specified values (built-in are zero.pages, zero.media and zero.url)
|
||||
* @returns {EditorField}
|
||||
*/
|
||||
linkPicker(options)
|
||||
|
||||
@@ -7,6 +7,7 @@ class Plugin
|
||||
routes = [];
|
||||
editors = [];
|
||||
lists = [];
|
||||
linkAreas = [];
|
||||
|
||||
|
||||
constructor(name)
|
||||
@@ -54,9 +55,9 @@ class Plugin
|
||||
/*
|
||||
* Add a new vue list renderer to the global configuration
|
||||
*/
|
||||
addList(config)
|
||||
addList(list)
|
||||
{
|
||||
this.lists.push(config);
|
||||
this.lists.push(list);
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +88,15 @@ class Plugin
|
||||
{
|
||||
routes.forEach(route => this.addRoute(route));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Add a new link area
|
||||
*/
|
||||
addLinkArea(area)
|
||||
{
|
||||
this.linkAreas.push(area);
|
||||
}
|
||||
};
|
||||
|
||||
export default Plugin;
|
||||
@@ -128,6 +128,8 @@ class Zero
|
||||
// append lists
|
||||
plugin.lists.forEach(x => this.addOrReplace(this.#lists, x, 'alias'));
|
||||
|
||||
// append
|
||||
|
||||
console.log(`[zero] Installed %c${plugin.name}%cplugin`, 'font-style:italic;');
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Api;
|
||||
using zero.Core.Entities;
|
||||
|
||||
namespace zero.Web.Controllers
|
||||
{
|
||||
public class LinkPickerController : BackofficeController
|
||||
{
|
||||
IPageTreeApi Api;
|
||||
IPagesApi PagesApi;
|
||||
|
||||
public LinkPickerController(IPageTreeApi api, IPagesApi pagesApi)
|
||||
{
|
||||
Api = api;
|
||||
PagesApi = pagesApi;
|
||||
}
|
||||
|
||||
|
||||
public async Task<IList<TreeItem>> GetChildren([FromQuery] string areaAlias, [FromQuery] string parent = null, [FromQuery] string active = null)
|
||||
{
|
||||
return await Api.GetChildren(parent, active);
|
||||
}
|
||||
|
||||
//public async Task<IList<PreviewModel>> GetPreviews([FromQuery] List<string> ids)
|
||||
//{
|
||||
// return Previews(await PagesApi.GetByIds(ids.ToArray()), PreviewTransform);
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,13 @@
|
||||
{
|
||||
"zero": {
|
||||
"name": "zero"
|
||||
"name": "zero",
|
||||
|
||||
"config": {
|
||||
"linkareas": {
|
||||
"pages": "Pages",
|
||||
"media": "Media"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"ui": {
|
||||
|
||||
Reference in New Issue
Block a user