phew
This commit is contained in:
@@ -54,6 +54,7 @@ public abstract class ZeroApiEntityStoreController<TModel, TStore> : ZeroApiCont
|
||||
protected async Task<ActionResult<Paged>> GetModels<T>(ListQuery<TModel> query) where T : BasicModel<TModel>
|
||||
{
|
||||
query.OrderQuery ??= q => q.OrderByDescending(x => x.CreatedDate);
|
||||
query.SearchSelector ??= x => x.Name;
|
||||
Paged<TModel> result = await Store.Load(query.Page, query.PageSize, q => q.Filter(query));
|
||||
|
||||
return Mapper.Map<TModel, T>(result, (src, dest) =>
|
||||
@@ -66,6 +67,7 @@ public abstract class ZeroApiEntityStoreController<TModel, TStore> : ZeroApiCont
|
||||
protected async Task<ActionResult<Paged>> GetModels<T, TIndex>(ListQuery<TModel> query) where T : BasicModel<TModel> where TIndex : AbstractCommonApiForIndexes, new()
|
||||
{
|
||||
query.OrderQuery ??= q => q.OrderByDescending(x => x.CreatedDate);
|
||||
query.SearchSelector ??= x => x.Name;
|
||||
Paged<TModel> result = await Store.Load<TIndex>(query.Page, query.PageSize, q => q.Filter(query));
|
||||
|
||||
return Mapper.Map<TModel, T>(result, (src, dest) =>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="root">
|
||||
about
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
data: () => ({
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="root">
|
||||
this is the root
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
data: () => ({
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -1,6 +1,14 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<div>
|
||||
<div class="ui-box" style="width: 600px">
|
||||
nav:
|
||||
<router-link to="/">Home</router-link>
|
||||
<router-link to="/about">About</router-link>
|
||||
<router-link to="/countries">Countries</router-link><br />
|
||||
view:
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
<div class="ui-box">
|
||||
<input type="text" v-model="input" />
|
||||
<br />
|
||||
output: {{output}}
|
||||
@@ -24,6 +32,28 @@
|
||||
Our settings <button type="button" @click="tabLabel = 'New Settings'">change label</button>
|
||||
</ui-tab>
|
||||
</ui-tabs>
|
||||
<br /><br /><br />
|
||||
buttons:
|
||||
<ui-button label="Click" type="accent"></ui-button>
|
||||
<ui-icon-button icon="fth-home" type="accent"></ui-icon-button>
|
||||
<ui-dot-button></ui-dot-button>
|
||||
<ui-select-button label="Choose" description="Select an item"></ui-select-button>
|
||||
<ui-state-button :items="[{value: 'yes', label: 'Yes'}, {value: 'no', label: 'No'}]"></ui-state-button>
|
||||
<br />
|
||||
message: <ui-message text="Warning, this is not acceptable" />
|
||||
<br />
|
||||
pagination: <ui-pagination :pages="10" :page="2"></ui-pagination>
|
||||
<br />
|
||||
rte:
|
||||
<br />
|
||||
<ui-rte v-model="rte" />
|
||||
<br />
|
||||
<ui-toggle v-model="toggled" />
|
||||
<br />
|
||||
<ui-search v-if="toggled" v-model="search" @submit="searchSubmit($event)" />
|
||||
<br />
|
||||
countrypicker:
|
||||
<ui-countrypicker v-if="!toggled" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -38,19 +68,24 @@
|
||||
data: () => ({
|
||||
input: null,
|
||||
name: localize('@name'),
|
||||
tabLabel: 'Settings'
|
||||
tabLabel: 'Settings',
|
||||
rte: '<p><b>Hallo</b><br>Das ist <i>schön</i></p><p>Ciao Tobi</p>',
|
||||
toggled: true,
|
||||
search: null
|
||||
}),
|
||||
|
||||
created()
|
||||
{
|
||||
console.info(this.zero.version);
|
||||
},
|
||||
|
||||
computed: {
|
||||
output()
|
||||
{
|
||||
return selectorToArray(this.input);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
searchSubmit(ev)
|
||||
{
|
||||
console.info('searched: ' + ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-7
@@ -1,13 +1,13 @@
|
||||
import Axios from 'axios';
|
||||
import Auth from 'zero/helpers/auth.js';
|
||||
//import Auth from 'zero/helpers/auth.js';
|
||||
import Qs from 'qs';
|
||||
|
||||
if (!__zero || !__zero.apiPath)
|
||||
{
|
||||
throw Exception('window.zero and zero.apiPath (= base path to the backoffice API) have to be configured');
|
||||
}
|
||||
//if (!__zero || !__zero.apiPath)
|
||||
//{
|
||||
// throw Exception('window.zero and zero.apiPath (= base path to the backoffice API) have to be configured');
|
||||
//}
|
||||
|
||||
Axios.defaults.baseURL = __zero.apiPath;
|
||||
Axios.defaults.baseURL = '/zero/api/';
|
||||
Axios.defaults.withCredentials = true;
|
||||
|
||||
Axios.defaults.paramsSerializer = (params) =>
|
||||
@@ -21,7 +21,8 @@ Axios.interceptors.response.use(response => response, error =>
|
||||
{
|
||||
if (error.response.status === 401)
|
||||
{
|
||||
Auth.rejectUser("@login.rejectReasons.inactive");
|
||||
console.error('[zero.axios] Auth failed. Please login again.');
|
||||
//Auth.rejectUser("@login.rejectReasons.inactive");
|
||||
//Notification.error('Authentication failed. Please login again.', 3);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import uiIcon from './ui-icon.vue';
|
||||
import uiLoading from './ui-loading.vue';
|
||||
|
||||
export {
|
||||
uiIcon,
|
||||
uiLoading
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import uiIcon from './ui-icon.vue';
|
||||
import uiLoading from './ui-loading.vue';
|
||||
import uiTab from './ui-tab.vue';
|
||||
import uiTabs from './ui-tabs.vue';
|
||||
import uiInlineTabs from './ui-inline-tabs.vue';
|
||||
import uiButton from './ui-button.vue';
|
||||
import uiDotButton from './ui-dot-button.vue';
|
||||
import uiIconButton from './ui-icon-button.vue';
|
||||
import uiSelectButton from './ui-select-button.vue';
|
||||
import uiStateButton from './ui-state-button.vue';
|
||||
import uiMessage from './ui-message.vue';
|
||||
import uiPagination from './ui-pagination.vue';
|
||||
import uiError from './ui-error.vue';
|
||||
|
||||
export {
|
||||
uiIcon,
|
||||
uiLoading,
|
||||
uiTab,
|
||||
uiTabs,
|
||||
uiInlineTabs,
|
||||
uiButton,
|
||||
uiDotButton,
|
||||
uiIconButton,
|
||||
uiSelectButton,
|
||||
uiStateButton,
|
||||
uiMessage,
|
||||
uiPagination,
|
||||
uiError
|
||||
};
|
||||
@@ -1,10 +1,13 @@
|
||||
|
||||
import { App } from 'vue';
|
||||
import registerGeneric from './generic/register';
|
||||
import registerTabs from './tabs/register';
|
||||
import * as components from './index';
|
||||
|
||||
export default function (app: App)
|
||||
{
|
||||
registerGeneric(app);
|
||||
registerTabs(app);
|
||||
for (var key in components)
|
||||
{
|
||||
let component = components[key];
|
||||
|
||||
app.component(key, component.default || component);
|
||||
}
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
import uiTab from './ui-tab.vue';
|
||||
import uiTabs from './ui-tabs.vue';
|
||||
import uiInlineTabs from './ui-inline-tabs.vue';
|
||||
|
||||
export
|
||||
{
|
||||
uiTab,
|
||||
uiTabs,
|
||||
uiInlineTabs
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
import { App } from 'vue';
|
||||
import * as components from './index';
|
||||
|
||||
export default function (app: App)
|
||||
{
|
||||
for (var key in components)
|
||||
{
|
||||
let component = components[key];
|
||||
|
||||
app.component(key, component.default || component);
|
||||
}
|
||||
};
|
||||
+1
-13
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button :type="buttonType" class="ui-button has-state" :class="buttonClass" :disabled="disabled || state == 'loading'" @click="tryClick">
|
||||
<button :type="buttonType" class="ui-button has-state" :class="buttonClass" :disabled="disabled || state == 'loading' || !isDefaultState">
|
||||
<span v-if="label" class="ui-button-text" v-localize:html="label"></span>
|
||||
<ui-icon v-if="caret" :symbol="caretSymbol" class="ui-button-caret" />
|
||||
<ui-icon v-if="icon" :symbol="icon" class="ui-button-icon" :stroke="stroke" />
|
||||
@@ -149,18 +149,6 @@
|
||||
}, this.stateDuration);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
tryClick(ev)
|
||||
{
|
||||
if (this.isDefaultState)
|
||||
{
|
||||
this.$emit('click', ev);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
+3
-4
@@ -6,8 +6,7 @@
|
||||
|
||||
|
||||
<script>
|
||||
import { isArray as _isArray } from 'underscore'
|
||||
import Strings from 'zero/helpers/strings.js'
|
||||
import { generateId } from '../utils/numbers';
|
||||
|
||||
export default {
|
||||
name: 'uiError',
|
||||
@@ -53,14 +52,14 @@
|
||||
return this.clearErrors();
|
||||
}
|
||||
|
||||
if (!_isArray(errors))
|
||||
if (!Array.isArray(errors))
|
||||
{
|
||||
errors = [errors];
|
||||
}
|
||||
|
||||
errors.forEach(error =>
|
||||
{
|
||||
error.id = Strings.guid();
|
||||
error.id = generateId();
|
||||
});
|
||||
|
||||
if (append)
|
||||
+4
-4
@@ -6,17 +6,17 @@
|
||||
<span class="ui-select-button-icon is-image" v-if="isImage">
|
||||
<img :src="source" :alt="icon" />
|
||||
</span>
|
||||
<content class="ui-select-button-content" v-if="label || description">
|
||||
<div class="ui-select-button-content" v-if="label || description">
|
||||
<strong class="ui-select-button-label" v-localize:html="{ key: label, tokens: tokens }"></strong>
|
||||
<span class="ui-select-button-description" v-if="description" v-localize:html="{ key: description, tokens: tokens }"></span>
|
||||
</content>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import MediaApi from 'zero/api/media.js';
|
||||
//import MediaApi from 'zero/api/media.js'; // TODO vue
|
||||
|
||||
export default {
|
||||
name: 'uiSelectButton',
|
||||
@@ -53,7 +53,7 @@
|
||||
},
|
||||
source()
|
||||
{
|
||||
return this.iconAsImage ? (this.icon.indexOf('/') === 0 ? this.icon : MediaApi.getImageSource(this.icon)) : null;
|
||||
return this.icon; //this.iconAsImage ? (this.icon.indexOf('/') === 0 ? this.icon : MediaApi.getImageSource(this.icon)) : null;
|
||||
}
|
||||
},
|
||||
|
||||
-2
@@ -8,8 +8,6 @@
|
||||
|
||||
|
||||
<script>
|
||||
import { find as _find } from 'underscore';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
disabled: {
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
|
||||
<script>
|
||||
import { generateId } from '../../utils';
|
||||
import { generateId } from '../utils';
|
||||
|
||||
export default {
|
||||
name: 'uiTab',
|
||||
-2
@@ -22,8 +22,6 @@
|
||||
|
||||
|
||||
<script>
|
||||
import { find as _find } from 'underscore';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
label: {
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
import { App } from 'vue';
|
||||
import { Router } from 'vue-router';
|
||||
import { ZeroRuntime, ZeroInstallOptions } from './index';
|
||||
|
||||
|
||||
@@ -12,7 +11,9 @@ export function createZeroPlugin(options?: ZeroInstallOptions)
|
||||
const zero = new ZeroRuntime(app, options);
|
||||
app.config.globalProperties.zero = zero;
|
||||
|
||||
zero.start();
|
||||
zero.useZero();
|
||||
zero.usePlugins();
|
||||
zero.useRouter();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -3,4 +3,5 @@ export * from './createZeroPlugin';
|
||||
export * from './zeroRuntime';
|
||||
export * from './types/zero';
|
||||
export * from './types/zeroPlugin';
|
||||
export * from './types/zeroInstallOptions';
|
||||
export * from './types/zeroInstallOptions';
|
||||
export * from './types/zeroPluginOptions';
|
||||
@@ -0,0 +1,18 @@
|
||||
import { RouteLocationNormalized, NavigationGuardNext, nav } from 'vue-router';
|
||||
|
||||
export default function (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext)
|
||||
{
|
||||
if (from.matched.length && from.matched[0].instances)
|
||||
{
|
||||
let instance = from.matched[0].instances.default;
|
||||
|
||||
if (instance.$refs['form'] && typeof instance.$refs.form.beforeRouteLeave === 'function')
|
||||
{
|
||||
isGuarded = true;
|
||||
instance.$refs.form.beforeRouteLeave(to, from, res =>
|
||||
{
|
||||
next(res !== false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
|
||||
import { Zero } from '../types/zero';
|
||||
import { createWebHistory, Router, RouterOptions } from 'vue-router';
|
||||
import titleGuard from './titleGuard';
|
||||
import formDirtyGuard from './formDirtyGuard';
|
||||
|
||||
export function getRouterConfig(basePath: string, zero: Zero): RouterOptions
|
||||
{
|
||||
let options = {
|
||||
history: createWebHistory(basePath),
|
||||
linkActiveClass: 'is-active',
|
||||
linkExactActiveClass: 'is-active-exact',
|
||||
scrollBehavior(to, from, savedPosition)
|
||||
{
|
||||
return savedPosition ? savedPosition : { x: 0, y: 0 };
|
||||
},
|
||||
routes: []
|
||||
} as RouterOptions;
|
||||
|
||||
//options.routes.push({
|
||||
// name: 'xroot',
|
||||
// path: '/',
|
||||
// component: () => import('../../_root.vue'),
|
||||
// meta: {
|
||||
// name: 'Root name',
|
||||
// alias: 'rootalias',
|
||||
// section: 'Root section'
|
||||
// }
|
||||
//} as RouteRecordRaw);
|
||||
|
||||
options.routes.push({ path: '/', component: () => import('../../_root.vue') });
|
||||
options.routes.push({ path: '/about', component: () => import('../../_about.vue') });
|
||||
|
||||
// TODO add zero routes
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
export function appendRouterGuards(router: Router): Router
|
||||
{
|
||||
//router.beforeEach(formDirtyGuard);
|
||||
//router.beforeEach(titleGuard);
|
||||
|
||||
return router;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { localize } from '../../services/localization';
|
||||
import { RouteLocationNormalized, NavigationGuardNext } from 'vue-router';
|
||||
|
||||
export default function (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext)
|
||||
{
|
||||
let title = localize('@zero.name');
|
||||
let name = to.meta.name;
|
||||
|
||||
if (!name && to.matched.length > 1)
|
||||
{
|
||||
to.matched.forEach(route =>
|
||||
{
|
||||
if (!name && route.meta.name)
|
||||
{
|
||||
name = route.meta.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!name || to.meta.alias === 'dashboard') //__zero.alias.sections.dashboard)
|
||||
{
|
||||
document.title = title;
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
let nameParts = Array.isArray(name) ? name : [name];
|
||||
let translations = [];
|
||||
|
||||
nameParts.forEach(part =>
|
||||
{
|
||||
if (part)
|
||||
{
|
||||
translations.push(localize(part));
|
||||
}
|
||||
});
|
||||
|
||||
title += ': ' + translations.join(' - ');
|
||||
|
||||
document.title = title;
|
||||
|
||||
next();
|
||||
}
|
||||
@@ -2,5 +2,7 @@
|
||||
export interface Zero
|
||||
{
|
||||
version: string;
|
||||
start: () => void;
|
||||
useZero: () => void;
|
||||
useRouter: () => void;
|
||||
usePlugins: () => void;
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
|
||||
import { App } from 'vue';
|
||||
import { Zero } from './zero';
|
||||
import { ZeroPluginOptions } from './zeroPluginOptions';
|
||||
|
||||
|
||||
export interface ZeroPlugin
|
||||
{
|
||||
name: string;
|
||||
install: (app: App, zero: Zero) => void;
|
||||
install: (zero: ZeroPluginOptions) => void;
|
||||
}
|
||||
|
||||
//export class Zero
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { App } from 'vue';
|
||||
|
||||
export interface ZeroPluginOptions
|
||||
{
|
||||
vue: App;
|
||||
routes: RouteRecordRaw[];
|
||||
route: (route: RouteRecordRaw) => void;
|
||||
}
|
||||
|
||||
//export class Zero
|
||||
//{
|
||||
// _app: App;
|
||||
// _options: ZeroInstallOptions;
|
||||
|
||||
// get version(): string
|
||||
// {
|
||||
// return "0.0.1";
|
||||
// }
|
||||
|
||||
// constructor(app: App, options?: ZeroInstallOptions)
|
||||
// {
|
||||
// this._app = app;
|
||||
// this._options = options || {};
|
||||
// console.info('[zero installed]');
|
||||
// }
|
||||
//}
|
||||
@@ -1,16 +1,24 @@
|
||||
|
||||
import { App } from 'vue';
|
||||
import { Router } from 'vue-router';
|
||||
import { ZeroInstallOptions } from './types/zeroInstallOptions';
|
||||
import { ZeroPluginOptions } from './types/zeroPluginOptions';
|
||||
import { Zero } from './types/zero';
|
||||
import { createRouter, RouteRecordRaw, RouterOptions } from 'vue-router';
|
||||
import registerDirectives from '../directives/register';
|
||||
import registerComponents from '../components/register';
|
||||
import registerFormComponents from '../forms/components/register';
|
||||
import { getRouterConfig, appendRouterGuards } from './router/routerConfig';
|
||||
import countryPlugin from '../modules/countries/plugin';
|
||||
|
||||
export class ZeroRuntime implements Zero
|
||||
{
|
||||
_app: App;
|
||||
_options: ZeroInstallOptions;
|
||||
_routerConfig: RouterOptions;
|
||||
|
||||
/**
|
||||
* version of zero backoffice
|
||||
**/
|
||||
get version(): string
|
||||
{
|
||||
return "0.0.1";
|
||||
@@ -20,13 +28,50 @@ export class ZeroRuntime implements Zero
|
||||
{
|
||||
this._app = app;
|
||||
this._options = options || {};
|
||||
|
||||
console.info('[zero installed]');
|
||||
this._routerConfig = getRouterConfig('/zero', this);
|
||||
}
|
||||
|
||||
start()
|
||||
|
||||
/**
|
||||
* register core components
|
||||
**/
|
||||
useZero()
|
||||
{
|
||||
registerDirectives(this._app);
|
||||
registerComponents(this._app);
|
||||
let app = this._app;
|
||||
|
||||
registerDirectives(app);
|
||||
registerComponents(app);
|
||||
registerFormComponents(app);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create plugin options which are passed to install() for all plugins
|
||||
**/
|
||||
usePlugins()
|
||||
{
|
||||
const pluginOptions = {
|
||||
vue: this._app,
|
||||
routes: this._routerConfig.routes,
|
||||
route(route: RouteRecordRaw)
|
||||
{
|
||||
this.routes.push(route);
|
||||
}
|
||||
} as ZeroPluginOptions;
|
||||
|
||||
// install all plugins
|
||||
countryPlugin.install(pluginOptions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* after all plugins were installed we can create the router
|
||||
* with the designated routes
|
||||
**/
|
||||
useRouter()
|
||||
{
|
||||
const router = createRouter(this._routerConfig);
|
||||
appendRouterGuards(router);
|
||||
this._app.use(router);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
export interface ZeroEditor
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export declare type BooleanExpression<TModel, TField> = (model: TModel, value: TField) => boolean;
|
||||
|
||||
|
||||
|
||||
export interface ZeroEditorField<TModel, TField>
|
||||
{
|
||||
when(condition: BooleanExpression<TModel, TField>): ZeroEditorField<TModel, TField>;
|
||||
|
||||
required(required: boolean): ZeroEditorField<TModel, TField>;
|
||||
|
||||
required(condition: BooleanExpression<TModel, TField>): ZeroEditorField<TModel, TField>;
|
||||
}
|
||||
|
||||
|
||||
var field = {
|
||||
when(condition)
|
||||
{
|
||||
return this;
|
||||
},
|
||||
required(required: boolean)
|
||||
{
|
||||
return this;
|
||||
},
|
||||
required(condition: BooleanExpression<any, any>)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
} as ZeroEditorField<any, any>;
|
||||
@@ -0,0 +1,11 @@
|
||||
import uiRte from './ui-rte/ui-rte.vue';
|
||||
import uiCheckList from './ui-check-list.vue';
|
||||
import uiSearch from './ui-search.vue';
|
||||
import uiToggle from './ui-toggle.vue';
|
||||
|
||||
export {
|
||||
uiRte,
|
||||
uiCheckList,
|
||||
uiSearch,
|
||||
uiToggle
|
||||
};
|
||||
+11
-15
@@ -11,8 +11,6 @@
|
||||
|
||||
|
||||
<script>
|
||||
import { map as _map } from 'underscore';
|
||||
|
||||
export default {
|
||||
name: 'uiInputList',
|
||||
|
||||
@@ -21,7 +19,7 @@
|
||||
type: String,
|
||||
default: '@ui.add'
|
||||
},
|
||||
value: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
@@ -48,28 +46,27 @@
|
||||
}),
|
||||
|
||||
watch: {
|
||||
value(value)
|
||||
modelValue(value)
|
||||
{
|
||||
this.items = _map(value, item =>
|
||||
{
|
||||
return { value: item };
|
||||
});
|
||||
this.reload();
|
||||
}
|
||||
},
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.items = _map(this.value, item =>
|
||||
{
|
||||
return { value: item };
|
||||
});
|
||||
this.reload();
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
reload()
|
||||
{
|
||||
this.items = this.modelValue.map(item => ({ value: item }));
|
||||
},
|
||||
|
||||
onChange()
|
||||
{
|
||||
this.$emit('input', _map(this.items, item => item.value));
|
||||
this.$emit('update:modelValue', this.items.map(item => item.value));
|
||||
},
|
||||
|
||||
addItem()
|
||||
@@ -98,7 +95,6 @@
|
||||
<style lang="scss">
|
||||
.ui-input-list
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
.ui-input-list-item
|
||||
@@ -111,7 +107,7 @@
|
||||
|
||||
.ui-input-list.is-disabled &
|
||||
{
|
||||
background: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.ui-input
|
||||
@@ -0,0 +1,174 @@
|
||||
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
|
||||
import Bold from '@tiptap/extension-bold';
|
||||
import Code from '@tiptap/extension-code';
|
||||
import Italic from '@tiptap/extension-italic';
|
||||
//import Link from '@tiptap/extension-link';
|
||||
import Strike from '@tiptap/extension-strike';
|
||||
//import Underline from '@tiptap/ex';
|
||||
import History from '@tiptap/extension-history';
|
||||
//import Placeholder from '@tiptap/extension-placeholder';
|
||||
import HorizontalRule from '@tiptap/extension-horizontal-rule';
|
||||
import ListItem from '@tiptap/extension-list-item';
|
||||
import BulletList from '@tiptap/extension-bullet-list';
|
||||
import OrderedList from '@tiptap/extension-ordered-list';
|
||||
import Heading from '@tiptap/extension-heading';
|
||||
import HardBreak from '@tiptap/extension-hard-break';
|
||||
|
||||
export default function ()
|
||||
{
|
||||
return {
|
||||
|
||||
removeExtension(name)
|
||||
{
|
||||
var ext = this.extensions.find(x => x.name === name);
|
||||
ext && this.extensions.splice(this.extensions.indexOf(ext), 1);
|
||||
},
|
||||
|
||||
removeCommand(alias)
|
||||
{
|
||||
var cmd = this.commands.find(x => x.alias === alias);
|
||||
cmd && this.commands.splice(this.commands.indexOf(cmd), 1);
|
||||
},
|
||||
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
History,
|
||||
HardBreak,
|
||||
//new Link({
|
||||
// target: '_blank'
|
||||
//}), // TODO
|
||||
Bold,
|
||||
Code,
|
||||
Italic,
|
||||
Strike,
|
||||
//Underline,
|
||||
ListItem,
|
||||
BulletList,
|
||||
OrderedList,
|
||||
HorizontalRule,
|
||||
Heading.configure({ // TODO
|
||||
levels: [2, 3, 4],
|
||||
})
|
||||
],
|
||||
|
||||
commands: [
|
||||
//{
|
||||
// alias: 'undo',
|
||||
// title: '@rte.undo',
|
||||
// symbol: 'fth-chevron-left',
|
||||
// disabled: cmd => cmd.undoDepth() > 1,
|
||||
// onClick: (ev, cmd) => cmd.undo(ev),
|
||||
//},
|
||||
//{
|
||||
// alias: 'redo',
|
||||
// title: '@rte.redo',
|
||||
// symbol: 'fth-chevron-right',
|
||||
// disabled: cmd => cmd.redoDepth() > 1,
|
||||
// onClick: (ev, cmd) => cmd.redo(ev)
|
||||
//},
|
||||
{
|
||||
alias: 'bold',
|
||||
title: '@rte.bold',
|
||||
symbol: 'fth-bold',
|
||||
symbolSize: 14,
|
||||
isActive: editor => editor.isActive('bold'),
|
||||
onClick: editor => editor.chain().focus().toggleBold().run(),
|
||||
bubble: true
|
||||
},
|
||||
{
|
||||
alias: 'italic',
|
||||
title: '@rte.italic',
|
||||
symbol: 'fth-italic',
|
||||
symbolSize: 14,
|
||||
isActive: editor => editor.isActive('italic'),
|
||||
onClick: editor => editor.chain().focus().toggleItalic().run(),
|
||||
bubble: true
|
||||
},
|
||||
{
|
||||
alias: 'underline',
|
||||
title: '@rte.underline',
|
||||
symbol: 'fth-underline',
|
||||
symbolSize: 14,
|
||||
isActive: editor => editor.isActive('underline'),
|
||||
onClick: editor => editor.chain().focus().toggleUnderline().run(),
|
||||
bubble: true
|
||||
},
|
||||
//{
|
||||
// alias: 'strikethrough',
|
||||
// title: '@rte.strike',
|
||||
// symbol: 'fth-zap-off',
|
||||
// symbolSize: 14,
|
||||
// isActive: active => active.strike(),
|
||||
// onClick: (ev, cmd) => cmd.strike(ev)
|
||||
//},
|
||||
{
|
||||
alias: 'code',
|
||||
title: '@rte.code',
|
||||
symbol: 'fth-code',
|
||||
symbolSize: 14,
|
||||
isActive: editor => editor.isActive('code'),
|
||||
onClick: editor => editor.chain().focus().toggleCode().run(),
|
||||
bubble: true
|
||||
},
|
||||
{
|
||||
alias: 'line',
|
||||
title: '@rte.line',
|
||||
symbol: 'fth-minus',
|
||||
symbolSize: 15,
|
||||
onClick: editor => editor.chain().focus().setHorizontalRule().run()
|
||||
},
|
||||
{
|
||||
alias: 'list',
|
||||
title: '@rte.list',
|
||||
symbol: 'fth-list',
|
||||
symbolSize: 14,
|
||||
children: [
|
||||
{
|
||||
alias: 'ulist',
|
||||
title: '@rte.ulist',
|
||||
isActive: editor => editor.isActive('bullet_list'),
|
||||
onClick: editor => editor.chain().focus().toggleBulletList().run()
|
||||
},
|
||||
{
|
||||
alias: 'olist',
|
||||
title: '@rte.olist',
|
||||
isActive: editor => editor.isActive('ordered_list'),
|
||||
onClick: editor => editor.chain().focus().toggleOrderedList().run()
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
alias: 'heading',
|
||||
title: '@rte.heading',
|
||||
symbol: 'fth-type',
|
||||
symbolSize: 14,
|
||||
children: [
|
||||
{
|
||||
alias: 'h2',
|
||||
title: '@rte.heading2',
|
||||
isActive: editor => editor.isActive('heading', { level: 2}),
|
||||
onClick: editor => editor.chain().focus().toggleHeading({ level: 2 }).run()
|
||||
},
|
||||
{
|
||||
alias: 'h3',
|
||||
title: '@rte.heading3',
|
||||
isActive: editor => editor.isActive('heading', { level: 3 }),
|
||||
onClick: editor => editor.chain().focus().toggleHeading({ level: 3 }).run()
|
||||
},
|
||||
{
|
||||
alias: 'h4',
|
||||
title: '@rte.heading4',
|
||||
isActive: editor => editor.isActive('heading', { level: 4 }),
|
||||
onClick: editor => editor.chain().focus().toggleHeading({ level: 4 }).run()
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
//import { Node, Extension, Plugin } from '@tiptap/vue-3';
|
||||
//import { chainCommands, exitCode } from 'prosemirror-commands';
|
||||
|
||||
//export class HardBreak extends Node
|
||||
//{
|
||||
// get name()
|
||||
// {
|
||||
// return 'br'
|
||||
// }
|
||||
|
||||
// get schema()
|
||||
// {
|
||||
// return {
|
||||
// inline: true,
|
||||
// group: 'inline',
|
||||
// selectable: false,
|
||||
// parseDOM: [
|
||||
// { tag: 'br' },
|
||||
// ],
|
||||
// toDOM: () => ['br'],
|
||||
// }
|
||||
// }
|
||||
|
||||
// keys({ type })
|
||||
// {
|
||||
// const command = chainCommands(exitCode, (state, dispatch) =>
|
||||
// {
|
||||
// dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView())
|
||||
// return true
|
||||
// })
|
||||
// return {
|
||||
// 'Enter': command
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
//export class MaxSize extends Extension
|
||||
//{
|
||||
// get name()
|
||||
// {
|
||||
// return 'maxSize'
|
||||
// }
|
||||
|
||||
// get defaultOptions()
|
||||
// {
|
||||
// return {
|
||||
// maxSize: null
|
||||
// }
|
||||
// }
|
||||
|
||||
// get plugins()
|
||||
// {
|
||||
// return [
|
||||
// new Plugin({
|
||||
// appendTransaction: (transactions, oldState, newState) =>
|
||||
// {
|
||||
// const max = this.options.maxSize;
|
||||
// const oldLength = oldState.doc.content.size;
|
||||
// const newLength = newState.doc.content.size;
|
||||
|
||||
// if (max && newLength > max && newLength > oldLength)
|
||||
// {
|
||||
// let newTr = newState.tr;
|
||||
// newTr.insertText('', max + 1, newLength);
|
||||
// return newTr;
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// ]
|
||||
// }
|
||||
//}
|
||||
+1
-1
@@ -100,7 +100,7 @@ export default {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.$scopedSlots.default({
|
||||
return this.$slots.default({
|
||||
focused: this.focused,
|
||||
focus: this.editor.focus,
|
||||
commands: this.editor.commands,
|
||||
+63
-60
@@ -1,34 +1,31 @@
|
||||
<template>
|
||||
<div class="ui-rte" :id="id" :disabled="disabled">
|
||||
|
||||
<editor-menu-bubble v-if="hasBubble" :editor="editor" :keep-in-bounds="true" v-slot="{ commands, isActive, menu }">
|
||||
<div class="ui-rte-overlay-controls theme-dark" :class="{ 'is-active': menu.isActive }" :style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`">
|
||||
<div class="ui-rte-overlay-control-outer" v-for="cmd in cmds" v-if="cmd.bubble && !cmd.isParent" :key="cmd.id">
|
||||
<button type="button" :data-alias="cmd.alias" v-localize:title="cmd.title" class="ui-rte-overlay-control" :class="{ 'is-active': cmd.isActive(isActive) }" @click="cmd.onClick($event, commands)">
|
||||
<!--<bubble-menu :editor="editor" :tippy-options="{ duration: 100 }" v-if="editor && hasBubble">
|
||||
<div class="ui-rte-overlay-controls theme-dark is-active">
|
||||
<div class="ui-rte-overlay-control-outer" v-for="cmd in editor.cmds" v-if="cmd && cmd.bubble && !cmd.isParent" :key="cmd.id">
|
||||
<button type="button" :data-alias="cmd.alias" v-localize:title="cmd.title" class="ui-rte-overlay-control" :class="{ 'is-active': cmd.isActive(editor) }" @click="cmd.onClick(editor)">
|
||||
<ui-icon :symbol="cmd.symbol" :size="cmd.symbolSize" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</editor-menu-bubble>
|
||||
</bubble-menu>-->
|
||||
|
||||
<editor-menu-bar v-if="cmds.length > 0 && editor" :editor="editor" v-slot="{ commands, isActive }">
|
||||
<div class="ui-rte-controls">
|
||||
<div class="ui-rte-control-outer" v-for="cmd in cmds" :key="cmd.id">
|
||||
<button type="button" v-if="!cmd.isParent" :data-alias="cmd.alias" v-localize:title="cmd.title" class="ui-rte-control" :class="{ 'is-active': cmd.isActive(isActive) }" @click="cmd.onClick($event, commands)">
|
||||
<!--// TODO :disabled="cmd.disabled(commands)" | this triggers a recursive loop error when used in multiple properties per editor-->
|
||||
<ui-icon :symbol="cmd.symbol" :size="cmd.symbolSize" />
|
||||
</button>
|
||||
<ui-dropdown v-if="cmd.isParent" align="right">
|
||||
<template v-slot:button>
|
||||
<button :data-alias="cmd.alias" type="button" v-localize:title="cmd.title" class="ui-rte-control" :class="{ 'is-active': cmd.isActive(isActive) }">
|
||||
<ui-icon :symbol="cmd.symbol" :size="cmd.symbolSize" />
|
||||
</button>
|
||||
</template>
|
||||
<ui-dropdown-button v-for="child in cmd.children" :key="child.id" :label="child.title" @click="child.onClick($event, commands)" />
|
||||
</ui-dropdown>
|
||||
</div>
|
||||
<div class="ui-rte-controls" v-if="editor">
|
||||
<div class="ui-rte-control-outer" v-for="cmd in cmds" :key="cmd.id">
|
||||
<button type="button" v-if="!cmd.isParent" :data-alias="cmd.alias" v-localize:title="cmd.title" class="ui-rte-control" :class="{ 'is-active': cmd.isActive(editor) }" @click="cmd.onClick(editor)">
|
||||
<ui-icon :symbol="cmd.symbol" :size="cmd.symbolSize" />
|
||||
</button>
|
||||
<!--<ui-dropdown v-if="cmd.isParent" align="right">
|
||||
<template v-slot:button>
|
||||
<button :data-alias="cmd.alias" type="button" v-localize:title="cmd.title" class="ui-rte-control" :class="{ 'is-active': cmd.isActive(editor) }">
|
||||
<ui-icon :symbol="cmd.symbol" :size="cmd.symbolSize" />
|
||||
</button>
|
||||
</template>
|
||||
<ui-dropdown-button v-for="child in cmd.children" :key="child.id" :label="child.title" @click="child.onClick(editor)" />
|
||||
</ui-dropdown>-->
|
||||
</div>
|
||||
</editor-menu-bar>
|
||||
</div>
|
||||
|
||||
<editor-content class="ui-rte-input" :editor="editor" />
|
||||
|
||||
@@ -36,22 +33,22 @@
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import Strings from 'zero/helpers/strings.js';
|
||||
import { debounce as _debounce } from 'underscore';
|
||||
import { Editor, EditorContent, EditorMenuBubble } from 'tiptap';
|
||||
import EditorMenuBar from './rte.menubar.js';
|
||||
import { MaxSize } from 'zero/config/rte.extensions.js';
|
||||
import { Placeholder } from 'tiptap-extensions';
|
||||
import createConfig from 'zero/config/rte.config.js';
|
||||
<script lang="ts">
|
||||
import { generateId } from '../../../utils/numbers';
|
||||
import { debounce } from '../../../utils/timing';
|
||||
import { Editor, EditorContent, BubbleMenu } from '@tiptap/vue-3';
|
||||
// import { Placeholder } from '@tiptap/vue-3';
|
||||
import EditorMenuBar from './rte.menubar';
|
||||
//import { MaxSize } from './rte.extensions';
|
||||
import createConfig from './rte.config';
|
||||
|
||||
export default {
|
||||
name: 'uiRte',
|
||||
|
||||
components: { EditorContent, EditorMenuBubble, EditorMenuBar },
|
||||
components: { EditorContent, BubbleMenu, EditorMenuBar },
|
||||
|
||||
props: {
|
||||
value: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
@@ -74,7 +71,7 @@
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
id: 'rte-' + Strings.guid(),
|
||||
id: 'rte-' + generateId(),
|
||||
blocked: false,
|
||||
onDebouncedChange: null,
|
||||
editor: null,
|
||||
@@ -83,7 +80,7 @@
|
||||
}),
|
||||
|
||||
watch: {
|
||||
value()
|
||||
modelValue()
|
||||
{
|
||||
if (!this.blocked)
|
||||
{
|
||||
@@ -107,7 +104,7 @@
|
||||
|
||||
created()
|
||||
{
|
||||
this.onDebouncedChange = _debounce(this.onChange, 350);
|
||||
this.onDebouncedChange = debounce(this.onChange, 350);
|
||||
},
|
||||
|
||||
mounted()
|
||||
@@ -133,48 +130,53 @@
|
||||
return params;
|
||||
});
|
||||
|
||||
if (this.placeholder)
|
||||
{
|
||||
this.extensions.push(new Placeholder({
|
||||
emptyEditorClass: 'is-editor-empty',
|
||||
emptyNodeClass: 'is-empty',
|
||||
emptyNodeText: this.placeholder,
|
||||
showOnlyWhenEditable: true,
|
||||
showOnlyCurrent: true
|
||||
}));
|
||||
}
|
||||
//if (this.placeholder)
|
||||
//{
|
||||
// this.extensions.push(new Placeholder({
|
||||
// emptyEditorClass: 'is-editor-empty',
|
||||
// emptyNodeClass: 'is-empty',
|
||||
// emptyNodeText: this.placeholder,
|
||||
// showOnlyWhenEditable: true,
|
||||
// showOnlyCurrent: true
|
||||
// }));
|
||||
//}
|
||||
|
||||
if (this.maxLength > 0)
|
||||
{
|
||||
this.extensions.push(new MaxSize({ maxSize: this.maxLength }));
|
||||
}
|
||||
//if (this.maxLength > 0)
|
||||
//{
|
||||
// this.extensions.push(new MaxSize({ maxSize: this.maxLength }));
|
||||
//}
|
||||
|
||||
this.editor = new Editor({
|
||||
editable: !this.disabled,
|
||||
extensions: this.extensions,
|
||||
onUpdate: opts => this.onDebouncedChange(opts.getHTML())
|
||||
onUpdate: ({ editor }) =>
|
||||
{
|
||||
this.onDebouncedChange(editor.getHTML());
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.cmds = this.cmds;
|
||||
|
||||
this.init();
|
||||
},
|
||||
|
||||
methods: {
|
||||
init()
|
||||
{
|
||||
this.editor.setContent(this.value);
|
||||
this.editor.commands.setContent(this.modelValue, false);
|
||||
},
|
||||
|
||||
onChange(content)
|
||||
{
|
||||
this.blocked = true;
|
||||
this.$emit('input', content);
|
||||
this.$emit('update:modelValue', content);
|
||||
this.$nextTick(() => this.blocked = false);
|
||||
},
|
||||
|
||||
mapCommand(cmd)
|
||||
{
|
||||
return {
|
||||
id: Strings.guid(),
|
||||
id: generateId(),
|
||||
alias: cmd.alias,
|
||||
title: cmd.title,
|
||||
symbol: cmd.symbol,
|
||||
@@ -199,7 +201,7 @@
|
||||
.ui-rte
|
||||
{
|
||||
background: var(--color-input);
|
||||
border-radius: var(--radius);
|
||||
border-radius: var(--radius);
|
||||
border: var(--color-input-border);
|
||||
}
|
||||
|
||||
@@ -359,7 +361,7 @@
|
||||
|
||||
.ui-rte-control-outer + .ui-rte-control-outer
|
||||
{
|
||||
margin-left: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.ui-rte-control:hover, .ui-rte-control.is-active
|
||||
@@ -385,23 +387,24 @@
|
||||
{
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
> :last-child
|
||||
{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,h6
|
||||
h1, h2, h3, h4, h5, h6
|
||||
{
|
||||
margin-bottom: .5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1
|
||||
h1
|
||||
{
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
h2
|
||||
h2
|
||||
{
|
||||
font-size: 1.2em;
|
||||
}
|
||||
@@ -414,7 +417,7 @@
|
||||
h4, h5, h6
|
||||
{
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
hr
|
||||
{
|
||||
@@ -423,7 +426,7 @@
|
||||
border-bottom-color: var(--color-line-dashed);
|
||||
}
|
||||
|
||||
li p
|
||||
li p
|
||||
{
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="ui-searchinput">
|
||||
<input ref="input" type="search" :value="value" @input="onChange" @keyup.enter="onSubmit" class="ui-input" v-localize:placeholder="placeholder" />
|
||||
<input ref="input" type="search" :value="modelValue" @input="onChange" @keyup.enter="onSubmit" class="ui-input" v-localize:placeholder="placeholder" />
|
||||
<slot name="button" v-bind="{ onSubmit: onSubmit }">
|
||||
<button type="button" class="ui-searchinput-button" v-localize:title="'@ui.search.button'" @click="onSubmit">
|
||||
<ui-icon symbol="fth-search" />
|
||||
@@ -15,7 +15,7 @@
|
||||
name: 'uiSearch',
|
||||
|
||||
props: {
|
||||
value: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
@@ -35,6 +35,7 @@
|
||||
{
|
||||
this.$emit('change', ev.target.value);
|
||||
this.$emit('input', ev.target.value);
|
||||
this.$emit('update:modelValue', ev.target.value);
|
||||
},
|
||||
|
||||
onSubmit(ev)
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="ui-native-select" :disabled="disabled">
|
||||
<select :value="value" @input="onChange" :disabled="disabled">
|
||||
<select :value="modelValue" @input="onChange" :disabled="disabled">
|
||||
<option v-if="emptyOption"></option>
|
||||
<option v-for="option in options" :value="option.key" v-localize="option.value"></option>
|
||||
</select>
|
||||
@@ -13,7 +13,7 @@
|
||||
name: 'uiSelect',
|
||||
|
||||
props: {
|
||||
value: [String, Number, Object],
|
||||
modelValue: [String, Number, Object],
|
||||
items: [Array, Function],
|
||||
entity: Object,
|
||||
disabled: Boolean,
|
||||
@@ -38,7 +38,7 @@
|
||||
handler()
|
||||
{
|
||||
this.rebuild();
|
||||
this.onChange({ target: { value: this.value } });
|
||||
this.onChange({ target: { value: this.modelValue } });
|
||||
}
|
||||
},
|
||||
entity: {
|
||||
@@ -46,7 +46,7 @@
|
||||
handler()
|
||||
{
|
||||
this.rebuild();
|
||||
this.onChange({ target: { value: this.value } });
|
||||
this.onChange({ target: { value: this.modelValue } });
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
onChange(ev)
|
||||
{
|
||||
this.$emit('input', ev.target.value ? ev.target.value : null);
|
||||
this.$emit('update:modelValue', ev.target.value ? ev.target.value : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="ui-toggle" :class="{'is-disabled': disabled, 'is-negative': negative, 'is-active': value, 'is-content-left': contentLeft }">
|
||||
<input type="checkbox" :value="value" @input="onChange" :disabled="disabled" />
|
||||
<span class="ui-toggle-switch" :class="{ 'is-active': value }"><i></i></span>
|
||||
<i class="fth-minus-circle ui-toggle-off-warning" v-if="offContent && !value && offWarning"></i>
|
||||
<span class="ui-toggle-text" v-if="onContent && value" v-localize="onContent"></span>
|
||||
<span class="ui-toggle-text" v-if="offContent && !value" v-localize="offContent"></span>
|
||||
<div class="ui-toggle" :class="{'is-disabled': disabled, 'is-negative': negative, 'is-active': modelValue, 'is-content-left': contentLeft }">
|
||||
<input type="checkbox" :value="modelValue" @input="onChange" :disabled="disabled" />
|
||||
<span class="ui-toggle-switch" :class="{ 'is-active': modelValue }"><i></i></span>
|
||||
<i class="fth-minus-circle ui-toggle-off-warning" v-if="offContent && !modelValue && offWarning"></i>
|
||||
<span class="ui-toggle-text" v-if="onContent && modelValue" v-localize="onContent"></span>
|
||||
<span class="ui-toggle-text" v-if="offContent && !modelValue" v-localize="offContent"></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
name: 'uiToggle',
|
||||
|
||||
props: {
|
||||
value: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
onChange(ev)
|
||||
{
|
||||
this.$emit('input', !this.value);
|
||||
this.$emit('update:modelValue', !this.modelValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import App from './app.vue';
|
||||
import { createZeroPlugin } from './core';
|
||||
import './axios.config.ts';
|
||||
|
||||
const app = createApp(App)
|
||||
.use(createPinia())
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="root">
|
||||
countries óÒ<br />
|
||||
<ui-search v-model="search" @submit="doSearch" />
|
||||
<div v-for="item in items" :key="item.id">
|
||||
{{item.name}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import api from './api';
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
items: [],
|
||||
search: null
|
||||
}),
|
||||
|
||||
methods: {
|
||||
async doSearch(val)
|
||||
{
|
||||
const result = await api.getByQuery({ search: val });
|
||||
this.items = result.items;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,16 @@
|
||||
import { get, post, put, del, ZeroRequestConfig, ZeroRequestQuery } from '../../services/request';
|
||||
|
||||
export default {
|
||||
|
||||
getEmpty: async (flavor?: string, config?: ZeroRequestConfig) => await get("countries/empty", { ...config, params: { flavor } }),
|
||||
|
||||
getById: async (id: string, changeVector?: string, config?: ZeroRequestConfig) => await get('countries/' + id, { ...config, params: { changeVector } }),
|
||||
|
||||
getByQuery: async (query: ZeroRequestQuery, config?: ZeroRequestConfig) => await get('countries', { ...config, params: { ...query } }),
|
||||
|
||||
create: async (model: any, config?: ZeroRequestConfig) => await post('countries', model, config),
|
||||
|
||||
update: async (model: any, config?: ZeroRequestConfig) => await put('countries/' + model.id, model, config),
|
||||
|
||||
delete: async (id: string, config?: ZeroRequestConfig) => await del('countries/' + id, config),
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ZeroPlugin, ZeroPluginOptions } from '../../core';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
|
||||
const Picker = () => import('./ui-countrypicker.vue');
|
||||
const Page = () => import('./_page.vue');
|
||||
|
||||
export default {
|
||||
name: "zero.countries",
|
||||
|
||||
install(app: ZeroPluginOptions)
|
||||
{
|
||||
app.vue.component('ui-countrypicker', defineAsyncComponent(Picker));
|
||||
app.route({ path: '/countries', component: Page });
|
||||
}
|
||||
} as ZeroPlugin;
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="ui-countrypicker" :class="{'is-disabled': disabled }">
|
||||
country picker
|
||||
<!--<ui-pick :config="pickerConfig" :value="value" @input="onChange" :disabled="disabled" />-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import api from './api';
|
||||
|
||||
export default {
|
||||
name: 'uiCountrypicker',
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Array],
|
||||
default: null
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
previews: [],
|
||||
pickerConfig: {}
|
||||
}),
|
||||
|
||||
//created()
|
||||
//{
|
||||
// this.pickerConfig = _extend({
|
||||
// scope: 'country',
|
||||
// items: CountriesApi.getForPicker,
|
||||
// previews: CountriesApi.getPreviews,
|
||||
// limit: this.limit,
|
||||
// multiple: this.limit > 1,
|
||||
// preview: {
|
||||
|
||||
// }
|
||||
// }, this.options);
|
||||
//},
|
||||
|
||||
|
||||
//methods: {
|
||||
// onChange(value)
|
||||
// {
|
||||
// this.$emit('input', value);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
</script>
|
||||
@@ -1,7 +1,34 @@
|
||||
|
||||
import axios from 'axios';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
|
||||
const getConfig = config =>
|
||||
export interface ZeroRequestConfig extends AxiosRequestConfig
|
||||
{
|
||||
scope?: string;
|
||||
}
|
||||
|
||||
export interface ZeroRequestQuery
|
||||
{
|
||||
/**
|
||||
* Current page index (starts at 1)
|
||||
**/
|
||||
page?: number;
|
||||
/**
|
||||
* Items per page (defaults to 30)
|
||||
**/
|
||||
pageSize?: number;
|
||||
/**
|
||||
* Limit query to specified IDs
|
||||
**/
|
||||
ids?: string[];
|
||||
/**
|
||||
* Search query string
|
||||
**/
|
||||
search?: string;
|
||||
}
|
||||
|
||||
|
||||
function getConfig(config?: ZeroRequestConfig): ZeroRequestConfig
|
||||
{
|
||||
config = config || {};
|
||||
if (config.scope)
|
||||
@@ -12,33 +39,33 @@ const getConfig = config =>
|
||||
return config;
|
||||
};
|
||||
|
||||
export async function get(url, config: any = null)
|
||||
export async function get(url: string, config?: ZeroRequestConfig)
|
||||
{
|
||||
return await send({ method: 'get', url, ...config });
|
||||
}
|
||||
|
||||
export async function post(url, data: any = null, config: any = null)
|
||||
export async function post(url: string, data: any, config?: ZeroRequestConfig)
|
||||
{
|
||||
return await send({ method: 'post', url, data, ...config });
|
||||
}
|
||||
|
||||
export async function del(url, config: any = null)
|
||||
export async function del(url: string, config?: ZeroRequestConfig)
|
||||
{
|
||||
return await send({ method: 'delete', url, ...config });
|
||||
}
|
||||
|
||||
export async function put(url, data: any = null, config: any = null)
|
||||
export async function put(url: string, data: any, config?: ZeroRequestConfig)
|
||||
{
|
||||
return await send({ method: 'put', url, data, ...config });
|
||||
}
|
||||
|
||||
export async function patch(url, data: any = null, config: any = null)
|
||||
export async function patch(url: string, data: any, config?: ZeroRequestConfig)
|
||||
{
|
||||
return await send({ method: 'patch', url, data, ...config });
|
||||
}
|
||||
|
||||
|
||||
export async function send(config)
|
||||
export async function send(config: ZeroRequestConfig)
|
||||
{
|
||||
config = getConfig(config);
|
||||
|
||||
@@ -54,63 +81,63 @@ export async function send(config)
|
||||
}
|
||||
|
||||
|
||||
export function collection(base)
|
||||
{
|
||||
return {
|
||||
getById: async (id, changeVector, config) => await get(base + 'getById', { ...config, params: { id, changeVector } }),
|
||||
getByIds: async (ids, config) => await get(base + 'getByIds', { ...config, params: { ids } }),
|
||||
getEmpty: async config => await get(base + 'getEmpty', { ...config }),
|
||||
getByQuery: async (query, config) => await get(base + 'getByQuery', { ...config, params: { query } }),
|
||||
getAll: async (config) => await get(base + 'getAll', { ...config }),
|
||||
getPreviews: async (ids, config) => await get(base + 'getPreviews', { ...config, params: { ids } }),
|
||||
getForPicker: async (config) => await get(base + 'getForPicker', { ...config }),
|
||||
getRevisions: async (id, query, config) => await get(base + 'getRevisions', { ...config, params: { id, query } }),
|
||||
save: async (model, config) => await post(base + 'save', model, { ...config }),
|
||||
delete: async (id, config) => await del(base + 'delete', { ...config, params: { id } })
|
||||
};
|
||||
}
|
||||
//export function collection(base)
|
||||
//{
|
||||
// return {
|
||||
// getById: async (id, changeVector, config) => await get(base + 'getById', { ...config, params: { id, changeVector } }),
|
||||
// getByIds: async (ids, config) => await get(base + 'getByIds', { ...config, params: { ids } }),
|
||||
// getEmpty: async config => await get(base + 'getEmpty', { ...config }),
|
||||
// getByQuery: async (query, config) => await get(base + 'getByQuery', { ...config, params: { query } }),
|
||||
// getAll: async (config) => await get(base + 'getAll', { ...config }),
|
||||
// getPreviews: async (ids, config) => await get(base + 'getPreviews', { ...config, params: { ids } }),
|
||||
// getForPicker: async (config) => await get(base + 'getForPicker', { ...config }),
|
||||
// getRevisions: async (id, query, config) => await get(base + 'getRevisions', { ...config, params: { id, query } }),
|
||||
// save: async (model, config) => await post(base + 'save', model, { ...config }),
|
||||
// delete: async (id, config) => await del(base + 'delete', { ...config, params: { id } })
|
||||
// };
|
||||
//}
|
||||
|
||||
|
||||
export function download(response)
|
||||
{
|
||||
let filename = response.headers["zero-filename"] || 'download.bin';
|
||||
//export function download(response)
|
||||
//{
|
||||
// let filename = response.headers["zero-filename"] || 'download.bin';
|
||||
|
||||
// code from: https://github.com/kennethjiang/js-file-download/blob/master/file-download.js
|
||||
// // code from: https://github.com/kennethjiang/js-file-download/blob/master/file-download.js
|
||||
|
||||
var blob = response.data;
|
||||
if (typeof window.navigator.msSaveBlob !== 'undefined')
|
||||
{
|
||||
// IE workaround for "HTML7007: One or more blob URLs were
|
||||
// revoked by closing the blob for which they were created.
|
||||
// These URLs will no longer resolve as the data backing
|
||||
// the URL has been freed."
|
||||
window.navigator.msSaveBlob(blob, filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
var blobURL = (window.URL && window.URL.createObjectURL) ? window.URL.createObjectURL(blob) : window.webkitURL.createObjectURL(blob);
|
||||
var tempLink = document.createElement('a');
|
||||
tempLink.style.display = 'none';
|
||||
tempLink.href = blobURL;
|
||||
tempLink.setAttribute('download', filename);
|
||||
// var blob = response.data;
|
||||
// if (typeof window.navigator.msSaveBlob !== 'undefined')
|
||||
// {
|
||||
// // IE workaround for "HTML7007: One or more blob URLs were
|
||||
// // revoked by closing the blob for which they were created.
|
||||
// // These URLs will no longer resolve as the data backing
|
||||
// // the URL has been freed."
|
||||
// window.navigator.msSaveBlob(blob, filename);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var blobURL = (window.URL && window.URL.createObjectURL) ? window.URL.createObjectURL(blob) : window.webkitURL.createObjectURL(blob);
|
||||
// var tempLink = document.createElement('a');
|
||||
// tempLink.style.display = 'none';
|
||||
// tempLink.href = blobURL;
|
||||
// tempLink.setAttribute('download', filename);
|
||||
|
||||
// Safari thinks _blank anchor are pop ups. We only want to set _blank
|
||||
// target if the browser does not support the HTML5 download attribute.
|
||||
// This allows you to download files in desktop safari if pop up blocking
|
||||
// is enabled.
|
||||
if (typeof tempLink.download === 'undefined')
|
||||
{
|
||||
tempLink.setAttribute('target', '_blank');
|
||||
}
|
||||
// // Safari thinks _blank anchor are pop ups. We only want to set _blank
|
||||
// // target if the browser does not support the HTML5 download attribute.
|
||||
// // This allows you to download files in desktop safari if pop up blocking
|
||||
// // is enabled.
|
||||
// if (typeof tempLink.download === 'undefined')
|
||||
// {
|
||||
// tempLink.setAttribute('target', '_blank');
|
||||
// }
|
||||
|
||||
document.body.appendChild(tempLink);
|
||||
tempLink.click();
|
||||
// document.body.appendChild(tempLink);
|
||||
// tempLink.click();
|
||||
|
||||
// Fixes "webkit blob resource error 1"
|
||||
setTimeout(function ()
|
||||
{
|
||||
document.body.removeChild(tempLink);
|
||||
window.URL.revokeObjectURL(blobURL);
|
||||
}, 200)
|
||||
}
|
||||
};
|
||||
// // Fixes "webkit blob resource error 1"
|
||||
// setTimeout(function ()
|
||||
// {
|
||||
// document.body.removeChild(tempLink);
|
||||
// window.URL.revokeObjectURL(blobURL);
|
||||
// }, 200)
|
||||
// }
|
||||
//};
|
||||
@@ -6,4 +6,5 @@ export * from './html';
|
||||
export * from './numbers';
|
||||
export * from './objects';
|
||||
export * from './videoparser';
|
||||
export * from './arrays';
|
||||
export * from './arrays';
|
||||
export * from './timing';
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
import { debounce as _debounce } from 'underscore';
|
||||
|
||||
/**
|
||||
* Proxy to underscore debounce
|
||||
*/
|
||||
export function debounce(fn, timespan)
|
||||
{
|
||||
return _debounce(fn, timespan)
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,10 +0,0 @@
|
||||
import { get, post, put, del } from '../helpers/request.ts';
|
||||
|
||||
export default {
|
||||
getById: async (id, config) => await get('countries/' + id, { ...config }),
|
||||
getEmpty: async config => await get('countries/empty', { ...config }),
|
||||
getByQuery: async (query, config) => await get('countries', { ...config, params: { query } }),
|
||||
create: async (model, config) => await post('countries', model, { ...config }),
|
||||
update: async (model, config) => await put('countries/' + model.id, model, { ...config }),
|
||||
delete: async (id, config) => await del('countries/' + id, { ...config, params: { id } })
|
||||
};
|
||||
@@ -1,135 +0,0 @@
|
||||
<template>
|
||||
<div class="ui-alias" :class="{'is-disabled': disabled, 'is-locked': locked }">
|
||||
<button type="button" class="ui-alias-lock" @click="toggleLock">
|
||||
<i :class="locked ? 'fth-lock' : 'fth-unlock'"></i>
|
||||
</button>
|
||||
<input ref="input" :value="value" @input="onChange" type="text" class="ui-input" :maxlength="maxLength" :readonly="locked" :disabled="disabled" @blur="locked=true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import { map as _map } from 'underscore';
|
||||
import UtilsApi from 'zero/api/utils.js';
|
||||
|
||||
export default {
|
||||
name: 'uiAlias',
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: null
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: 200
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
data: () => ({
|
||||
custom: false,
|
||||
locked: true
|
||||
}),
|
||||
|
||||
|
||||
watch: {
|
||||
name: function(val)
|
||||
{
|
||||
this.updateAlias(val);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.updateAlias(this.name);
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
|
||||
onChange(ev)
|
||||
{
|
||||
let alias = ev.target.value;
|
||||
|
||||
this.custom = alias !== this.value;
|
||||
this.updateAlias(alias);
|
||||
|
||||
this.$emit('input', alias);
|
||||
},
|
||||
|
||||
|
||||
toggleLock()
|
||||
{
|
||||
this.locked = !this.locked;
|
||||
|
||||
if (!this.locked)
|
||||
{
|
||||
this.$nextTick(() =>
|
||||
{
|
||||
this.$refs.input.focus();
|
||||
this.$refs.input.select();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
updateAlias(value)
|
||||
{
|
||||
UtilsApi.generateAlias(value).then(alias =>
|
||||
{
|
||||
this.custom = alias !== this.value;
|
||||
this.$emit('input', alias);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.ui-alias
|
||||
{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ui-alias button
|
||||
{
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: var(--radius);
|
||||
background: var(--color-box-nested);
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.ui-alias input
|
||||
{
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
height: 24px !important;
|
||||
padding: 0 !important;
|
||||
outline: none !important;
|
||||
min-width: 10px !important;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.ui-alias:not(.is-locked) input
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@@ -1,139 +0,0 @@
|
||||
<template>
|
||||
<div class="ui-check-list" :class="{'is-disabled': disabled, 'is-inline': inline }">
|
||||
<label v-for="item in list" class="ui-native-check ui-check-list-item">
|
||||
<input type="checkbox" :checked="isChecked(item)" @input="onChange(item)" :disabled="disabled" />
|
||||
<span class="ui-native-check-toggle"></span>
|
||||
<span v-localize="item[labelKey]"></span>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import { map as _map, filter as _filter } from 'underscore';
|
||||
|
||||
export default {
|
||||
name: 'uiCheckList',
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
items: {
|
||||
type: [Array, Function, Promise],
|
||||
required: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
reverse: Boolean,
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
idKey: {
|
||||
type: String,
|
||||
default: 'key'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
data: () => ({
|
||||
list: []
|
||||
}),
|
||||
|
||||
|
||||
watch: {
|
||||
items()
|
||||
{
|
||||
this.init();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.init();
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
|
||||
init()
|
||||
{
|
||||
if (typeof this.items === 'function')
|
||||
{
|
||||
this.items().then(res =>
|
||||
{
|
||||
this.list = res;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
this.list = JSON.parse(JSON.stringify(this.items));
|
||||
}
|
||||
},
|
||||
|
||||
isChecked(item)
|
||||
{
|
||||
let index = this.value.indexOf(item[this.idKey]);
|
||||
return (!this.reverse && index > -1) || (this.reverse && index < 0);
|
||||
},
|
||||
|
||||
onChange(item)
|
||||
{
|
||||
let index = this.value.indexOf(item[this.idKey]);
|
||||
let value = JSON.parse(JSON.stringify(this.value));
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
value.push(item[this.idKey]);
|
||||
}
|
||||
else
|
||||
{
|
||||
value.splice(index, 1);
|
||||
}
|
||||
|
||||
this.$emit('input', value);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-check-list-item
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
.ui-check-list .ui-check-list-item + .ui-check-list-item
|
||||
{
|
||||
margin-top: 8px;
|
||||
}
|
||||
.ui-alias + .ui-check-list-item
|
||||
{
|
||||
margin-top: 14px;
|
||||
}
|
||||
.ui-check-list.is-inline .ui-check-list-item
|
||||
{
|
||||
display: inline-block;
|
||||
}
|
||||
.ui-check-list.is-inline .ui-check-list-item + .ui-check-list-item
|
||||
{
|
||||
margin-top: 0;
|
||||
margin-left: 30px;
|
||||
}
|
||||
.ui-check-list.is-inline .ui-check-list-item .ui-native-check-toggle
|
||||
{
|
||||
margin-right: 6px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,199 +0,0 @@
|
||||
<template>
|
||||
<div class="ui-quill" :disabled="disabled">
|
||||
<div ref="editor" :id="id">
|
||||
<editor-menu-bubble :editor="editor" :keep-in-bounds="keepInBounds" v-slot="{ commands, isActive, menu }">
|
||||
<div class="menububble"
|
||||
:class="{ 'is-active': menu.isActive }"
|
||||
:style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`">
|
||||
|
||||
<button type="button" class="menububble__button"
|
||||
:class="{ 'is-active': isActive.bold() }"
|
||||
@click="commands.bold">
|
||||
B
|
||||
</button>
|
||||
|
||||
<button type="button" class="menububble__button"
|
||||
:class="{ 'is-active': isActive.italic() }"
|
||||
@click="commands.italic">
|
||||
I
|
||||
</button>
|
||||
|
||||
<button type="button" class="menububble__button"
|
||||
:class="{ 'is-active': isActive.code() }"
|
||||
@click="commands.code">
|
||||
C
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</editor-menu-bubble>
|
||||
|
||||
<editor-content class="editor__content" :editor="editor" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import Strings from 'zero/helpers/strings.js';
|
||||
import { Editor, EditorContent, EditorMenuBubble } from 'tiptap'
|
||||
import
|
||||
{
|
||||
Blockquote,
|
||||
BulletList,
|
||||
CodeBlock,
|
||||
HardBreak,
|
||||
Heading,
|
||||
ListItem,
|
||||
OrderedList,
|
||||
TodoItem,
|
||||
TodoList,
|
||||
Bold,
|
||||
Code,
|
||||
Italic,
|
||||
Link,
|
||||
Strike,
|
||||
Underline,
|
||||
History,
|
||||
} from 'tiptap-extensions'
|
||||
|
||||
export default {
|
||||
name: 'uiQuill',
|
||||
|
||||
components: { EditorContent, EditorMenuBubble },
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
id: 'quill-' + Strings.guid(),
|
||||
blocked: false,
|
||||
keepInBounds: true,
|
||||
editor: new Editor({
|
||||
extensions: [
|
||||
new Blockquote(),
|
||||
new BulletList(),
|
||||
new CodeBlock(),
|
||||
new HardBreak(),
|
||||
new Heading({ levels: [1, 2, 3] }),
|
||||
new ListItem(),
|
||||
new OrderedList(),
|
||||
new TodoItem(),
|
||||
new TodoList(),
|
||||
new Link(),
|
||||
new Bold(),
|
||||
new Code(),
|
||||
new Italic(),
|
||||
new Strike(),
|
||||
new Underline(),
|
||||
new History(),
|
||||
],
|
||||
})
|
||||
}),
|
||||
|
||||
created()
|
||||
{
|
||||
|
||||
},
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.initialize();
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
initialize()
|
||||
{
|
||||
|
||||
},
|
||||
|
||||
|
||||
setValue()
|
||||
{
|
||||
|
||||
},
|
||||
|
||||
|
||||
onLinkCreate()
|
||||
{
|
||||
|
||||
},
|
||||
|
||||
|
||||
acceptHtml()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
beforeDestroy()
|
||||
{
|
||||
this.editor.destroy();
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.menububble {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
z-index: 20;
|
||||
background: black;
|
||||
border-radius: 5px;
|
||||
padding: 0.3rem;
|
||||
margin-bottom: 0.5rem;
|
||||
transform: translateX(-50%);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s, visibility 0.2s;
|
||||
|
||||
&.is-active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
&__button {
|
||||
display: inline-flex;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: white;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-right: 0.2rem;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(white, 0.1);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: rgba(white, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
&__form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__input {
|
||||
font: inherit;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<div class="ui-countrypicker" :class="{'is-disabled': disabled }">
|
||||
<ui-pick :config="pickerConfig" :value="value" @input="onChange" :disabled="disabled" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import CountriesApi from 'zero/api/countries.js'
|
||||
import { extend as _extend } from 'underscore'
|
||||
|
||||
export default {
|
||||
name: 'uiCountrypicker',
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Array],
|
||||
default: null
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
previews: [],
|
||||
pickerConfig: {}
|
||||
}),
|
||||
|
||||
created()
|
||||
{
|
||||
this.pickerConfig = _extend({
|
||||
scope: 'country',
|
||||
items: CountriesApi.getForPicker,
|
||||
previews: CountriesApi.getPreviews,
|
||||
limit: this.limit,
|
||||
multiple: this.limit > 1,
|
||||
preview: {
|
||||
|
||||
}
|
||||
}, this.options);
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
onChange(value)
|
||||
{
|
||||
this.$emit('input', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,103 +0,0 @@
|
||||
<template>
|
||||
<div class="ui-dashboard-element" :class="classList">
|
||||
<header class="ui-dashboard-element-head">
|
||||
<h2 class="ui-headline ui-dashboard-element-title">Element</h2>
|
||||
<ui-icon-button icon="fth-more-horizontal" />
|
||||
</header>
|
||||
<content class="ui-dashboard-element-content">
|
||||
|
||||
</content>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'uiDashboardElement',
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
wide: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
high: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
id: null
|
||||
}),
|
||||
|
||||
computed: {
|
||||
classList()
|
||||
{
|
||||
return {
|
||||
'is-high': this.high,
|
||||
'is-wide': this.wide,
|
||||
['theme-' + this.theme]: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$dashboard-element-padding: 20px;
|
||||
|
||||
.ui-dashboard-element
|
||||
{
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
position: relative;
|
||||
background: var(--color-box);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-short);
|
||||
color: var(--color-text);
|
||||
|
||||
&.is-wide
|
||||
{
|
||||
grid-column-end: span 2;
|
||||
}
|
||||
|
||||
&.is-high
|
||||
{
|
||||
grid-row-end: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-dashboard-element-content
|
||||
{
|
||||
padding: $dashboard-element-padding;
|
||||
}
|
||||
|
||||
.ui-dashboard-element-head
|
||||
{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px $dashboard-element-padding;
|
||||
/*border-bottom: 1px solid var(--color-line-light);*/
|
||||
|
||||
.ui-icon-button
|
||||
{
|
||||
background: none;
|
||||
margin-right: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
h2.ui-dashboard-element-title
|
||||
{
|
||||
margin: 0;
|
||||
font-size: var(--font-size);
|
||||
}
|
||||
</style>
|
||||
@@ -11,12 +11,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import DashboardElement from '../../dashboard/element.vue';
|
||||
|
||||
export default {
|
||||
components: { DashboardElement }
|
||||
}
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
"flatpickr": "^4.6.9",
|
||||
"qs": "^6.10.2",
|
||||
"sortablejs": "^1.14.0",
|
||||
"tiptap": "^1.32.2",
|
||||
"tiptap-extensions": "^1.35.2",
|
||||
"@tiptap/vue-3": "2.0.0-beta.82",
|
||||
"@tiptap/starter-kit": "2.0.0-beta.153",
|
||||
"underscore": "^1.13.1",
|
||||
"vue": "^3.2.24",
|
||||
"vue-router": "^4.0.12",
|
||||
|
||||
@@ -57,7 +57,15 @@ fs.writeFile(path.resolve(__dirname, 'app/plugins.generated.js'), pluginFileCont
|
||||
let config = defineConfig({
|
||||
server: {
|
||||
port: process.env.PORT || 3399,
|
||||
cors: true
|
||||
cors: true,
|
||||
proxy: {
|
||||
'/zero/api': {
|
||||
target: 'http://localhost:2320',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
ws: false
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [vue(), ...zeroPlugins],
|
||||
build: {
|
||||
@@ -67,6 +75,9 @@ let config = defineConfig({
|
||||
terserOptions: {
|
||||
compress: false
|
||||
},
|
||||
//alias: {
|
||||
// 'tiptap': 'tiptap/dist/tiptap.esm.js',
|
||||
//},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
format: 'es',
|
||||
@@ -83,6 +94,7 @@ let config = defineConfig({
|
||||
if (process.env.NODE_ENV === 'production')
|
||||
{
|
||||
config.base = '/zero/';
|
||||
//config.alias.tiptap = 'node_modules/tiptap/dist/tiptap.esm.js';
|
||||
}
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user