phew
This commit is contained in:
@@ -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
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div class="ui-input-list" :class="{'is-disabled': disabled }">
|
||||
<div v-for="item in items" class="ui-input-list-item">
|
||||
<input v-model="item.value" type="text" class="ui-input" :maxlength="maxLength" :readonly="disabled" @input="onChange" size="5" />
|
||||
<ui-icon-button type="light" icon="fth-x" @click="removeItem(item)" v-if="!disabled" />
|
||||
</div>
|
||||
<ui-button v-if="!disabled && !plusButton" type="light" :label="addLabel" @click="addItem" />
|
||||
<ui-select-button v-if="!disabled && plusButton" icon="fth-plus" :label="items.length > 0 ? null : addLabel" @click="addItem" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'uiInputList',
|
||||
|
||||
props: {
|
||||
addLabel: {
|
||||
type: String,
|
||||
default: '@ui.add'
|
||||
},
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxItems: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
plusButton: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
items: []
|
||||
}),
|
||||
|
||||
watch: {
|
||||
modelValue(value)
|
||||
{
|
||||
this.reload();
|
||||
}
|
||||
},
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.reload();
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
reload()
|
||||
{
|
||||
this.items = this.modelValue.map(item => ({ value: item }));
|
||||
},
|
||||
|
||||
onChange()
|
||||
{
|
||||
this.$emit('update:modelValue', this.items.map(item => item.value));
|
||||
},
|
||||
|
||||
addItem()
|
||||
{
|
||||
this.items.push({
|
||||
value: null
|
||||
});
|
||||
this.onChange();
|
||||
|
||||
this.$nextTick(() =>
|
||||
{
|
||||
this.$el.querySelector('.ui-input-list-item:last-of-type input').focus();
|
||||
});
|
||||
},
|
||||
|
||||
removeItem(item)
|
||||
{
|
||||
const index = this.items.indexOf(item);
|
||||
this.items.splice(index, 1);
|
||||
this.onChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-input-list
|
||||
{
|
||||
}
|
||||
|
||||
.ui-input-list-item
|
||||
{
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
background: var(--color-input);
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 6px;
|
||||
|
||||
.ui-input-list.is-disabled &
|
||||
{
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.ui-input
|
||||
{
|
||||
border-radius: var(--radius) 0 0 var(--radius);
|
||||
}
|
||||
|
||||
.ui-icon-button
|
||||
{
|
||||
border-radius: 0 var(--radius) var(--radius) 0;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
border-left: none;
|
||||
background: transparent !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ui-icon-button + .ui-icon-button
|
||||
{
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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;
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// ]
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { Plugin, PluginKey } from 'prosemirror-state'
|
||||
|
||||
class Menu
|
||||
{
|
||||
|
||||
constructor({ options })
|
||||
{
|
||||
this.options = options
|
||||
this.preventHide = false
|
||||
|
||||
// the mousedown event is fired before blur so we can prevent it
|
||||
this.mousedownHandler = this.handleClick.bind(this)
|
||||
this.options.element.addEventListener('mousedown', this.mousedownHandler, { capture: true })
|
||||
|
||||
this.blurHandler = () =>
|
||||
{
|
||||
if (this.preventHide)
|
||||
{
|
||||
this.preventHide = false
|
||||
return
|
||||
}
|
||||
|
||||
this.options.editor.emit('menubar:focusUpdate', false)
|
||||
}
|
||||
this.options.editor.on('blur', this.blurHandler)
|
||||
}
|
||||
|
||||
handleClick()
|
||||
{
|
||||
this.preventHide = true
|
||||
}
|
||||
|
||||
destroy()
|
||||
{
|
||||
this.options.element.removeEventListener('mousedown', this.mousedownHandler)
|
||||
this.options.editor.off('blur', this.blurHandler)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const MenuBar = function (options)
|
||||
{
|
||||
return new Plugin({
|
||||
key: new PluginKey('menu_bar'),
|
||||
view(editorView)
|
||||
{
|
||||
return new Menu({ editorView, options })
|
||||
},
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
focused: false,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
editor: {
|
||||
immediate: true,
|
||||
handler(editor) {
|
||||
if (editor) {
|
||||
this.$nextTick(() =>
|
||||
{
|
||||
var menubar = MenuBar({
|
||||
editor,
|
||||
element: this.$el,
|
||||
});
|
||||
|
||||
editor.registerPlugin(menubar);
|
||||
|
||||
this.focused = editor.focused
|
||||
|
||||
editor.on('focus', () => {
|
||||
this.focused = true
|
||||
})
|
||||
|
||||
editor.on('menubar:focusUpdate', focused => {
|
||||
this.focused = focused
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
if (!this.editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.$slots.default({
|
||||
focused: this.focused,
|
||||
focus: this.editor.focus,
|
||||
commands: this.editor.commands,
|
||||
isActive: this.editor.isActive,
|
||||
getMarkAttrs: this.editor.getMarkAttrs.bind(this.editor),
|
||||
getNodeAttrs: this.editor.getNodeAttrs.bind(this.editor),
|
||||
})
|
||||
},
|
||||
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
<template>
|
||||
<div class="ui-rte" :id="id" :disabled="disabled">
|
||||
|
||||
<!--<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>
|
||||
</bubble-menu>-->
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<editor-content class="ui-rte-input" :editor="editor" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<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, BubbleMenu, EditorMenuBar },
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
setup: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
id: 'rte-' + generateId(),
|
||||
blocked: false,
|
||||
onDebouncedChange: null,
|
||||
editor: null,
|
||||
extensions: [],
|
||||
cmds: []
|
||||
}),
|
||||
|
||||
watch: {
|
||||
modelValue()
|
||||
{
|
||||
if (!this.blocked)
|
||||
{
|
||||
this.init();
|
||||
}
|
||||
},
|
||||
disabled(value)
|
||||
{
|
||||
this.editor.setOptions({
|
||||
editable: !value
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasBubble()
|
||||
{
|
||||
return this.cmds.find(x => x.bubble) != null;
|
||||
}
|
||||
},
|
||||
|
||||
created()
|
||||
{
|
||||
this.onDebouncedChange = debounce(this.onChange, 350);
|
||||
},
|
||||
|
||||
mounted()
|
||||
{
|
||||
var config = createConfig();
|
||||
|
||||
if (typeof this.setup === 'function')
|
||||
{
|
||||
this.setup(config);
|
||||
}
|
||||
|
||||
this.extensions = config.extensions;
|
||||
this.cmds = config.commands.map(cmd =>
|
||||
{
|
||||
let params = this.mapCommand(cmd);
|
||||
|
||||
if (cmd.children && cmd.children.length)
|
||||
{
|
||||
params.isParent = true;
|
||||
params.children = cmd.children.map(child => this.mapCommand(child));
|
||||
}
|
||||
|
||||
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.maxLength > 0)
|
||||
//{
|
||||
// this.extensions.push(new MaxSize({ maxSize: this.maxLength }));
|
||||
//}
|
||||
|
||||
this.editor = new Editor({
|
||||
editable: !this.disabled,
|
||||
extensions: this.extensions,
|
||||
onUpdate: ({ editor }) =>
|
||||
{
|
||||
this.onDebouncedChange(editor.getHTML());
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.cmds = this.cmds;
|
||||
|
||||
this.init();
|
||||
},
|
||||
|
||||
methods: {
|
||||
init()
|
||||
{
|
||||
this.editor.commands.setContent(this.modelValue, false);
|
||||
},
|
||||
|
||||
onChange(content)
|
||||
{
|
||||
this.blocked = true;
|
||||
this.$emit('update:modelValue', content);
|
||||
this.$nextTick(() => this.blocked = false);
|
||||
},
|
||||
|
||||
mapCommand(cmd)
|
||||
{
|
||||
return {
|
||||
id: generateId(),
|
||||
alias: cmd.alias,
|
||||
title: cmd.title,
|
||||
symbol: cmd.symbol,
|
||||
symbolSize: cmd.symbolSize || 17,
|
||||
isActive: typeof cmd.isActive === 'function' ? cmd.isActive : () => false,
|
||||
disabled: typeof cmd.disabled === 'function' ? cmd.disabled : () => false,
|
||||
onClick: typeof cmd.onClick === 'function' ? cmd.onClick : () => { },
|
||||
bubble: cmd.bubble || false
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy()
|
||||
{
|
||||
this.editor.destroy();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.ui-rte
|
||||
{
|
||||
background: var(--color-input);
|
||||
border-radius: var(--radius);
|
||||
border: var(--color-input-border);
|
||||
}
|
||||
|
||||
.ui-rte:focus-within
|
||||
{
|
||||
background-color: var(--color-input-focus-bg);
|
||||
border: var(--color-input-focus-border);
|
||||
box-shadow: var(--color-input-focus-shadow);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ui-rte-input
|
||||
{
|
||||
height: auto;
|
||||
min-height: 48px;
|
||||
padding-top: 9px;
|
||||
max-height: 420px;
|
||||
overflow-y: auto;
|
||||
border: none !important;
|
||||
|
||||
.ProseMirror:focus
|
||||
{
|
||||
outline: none;
|
||||
}
|
||||
|
||||
p
|
||||
{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p + p
|
||||
{
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
p.is-editor-empty:first-child:before
|
||||
{
|
||||
content: attr(data-empty-text);
|
||||
float: left;
|
||||
color: var(--color-input-placeholder);
|
||||
opacity: .7;
|
||||
pointer-events: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
a
|
||||
{
|
||||
color: var(--color-primary);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-rte-overlay-controls
|
||||
{
|
||||
position: absolute;
|
||||
display: flex;
|
||||
z-index: 20;
|
||||
background: var(--color-bg);
|
||||
border-radius: var(--radius);
|
||||
padding: 5px;
|
||||
margin-bottom: 8px;
|
||||
transform: translateX(-50%);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s, visibility 0.2s;
|
||||
pointer-events: none;
|
||||
|
||||
&:after
|
||||
{
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 100%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 8px solid transparent;
|
||||
margin-left: -7px;
|
||||
border-top-color: var(--color-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.ui-rte-overlay-controls.is-active
|
||||
{
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.ui-rte-overlay-control
|
||||
{
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: var(--color-text);
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui-rte-overlay-control + .ui-rte-overlay-control
|
||||
{
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.ui-rte-overlay-control:hover
|
||||
{
|
||||
background-color: var(--color-bg-shade-1);
|
||||
}
|
||||
|
||||
.ui-rte-overlay-control.is-active
|
||||
{
|
||||
background-color: var(--color-bg-shade-1);
|
||||
color: var(--color-text-dim);
|
||||
}
|
||||
|
||||
|
||||
.ui-rte-controls
|
||||
{
|
||||
background: none;
|
||||
padding: 5px 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
border-bottom: var(--color-input-border);
|
||||
}
|
||||
|
||||
.ui-rte-controls-label
|
||||
{
|
||||
color: var(--color-text-dim);
|
||||
margin: 0 14px 0 8px;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ui-rte-control
|
||||
{
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.ui-rte-control[disabled]
|
||||
{
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.ui-rte-control-outer + .ui-rte-control-outer
|
||||
{
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.ui-rte-control:hover, .ui-rte-control.is-active
|
||||
{
|
||||
background: var(--color-box-nested);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.ui-rte-input
|
||||
{
|
||||
background: none !important;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.ui-rte-input .ProseMirror
|
||||
{
|
||||
> *
|
||||
{
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
> :first-child
|
||||
{
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
> :last-child
|
||||
{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
{
|
||||
margin-bottom: .5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1
|
||||
{
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
h2
|
||||
{
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
h3
|
||||
{
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
h4, h5, h6
|
||||
{
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
hr
|
||||
{
|
||||
display: block;
|
||||
border-bottom-style: dashed;
|
||||
border-bottom-color: var(--color-line-dashed);
|
||||
}
|
||||
|
||||
li p
|
||||
{
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="ui-searchinput">
|
||||
<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" />
|
||||
</button>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'uiSearch',
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '@ui.search.placeholder'
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
onChange(ev)
|
||||
{
|
||||
this.$emit('change', ev.target.value);
|
||||
this.$emit('input', ev.target.value);
|
||||
this.$emit('update:modelValue', ev.target.value);
|
||||
},
|
||||
|
||||
onSubmit(ev)
|
||||
{
|
||||
this.$emit('submit', this.$refs.input.value);
|
||||
},
|
||||
|
||||
focus()
|
||||
{
|
||||
this.$refs.input.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-searchinput
|
||||
{
|
||||
position: relative;
|
||||
|
||||
.ui-input
|
||||
{
|
||||
display: block;
|
||||
min-width: 320px;
|
||||
padding-right: 40px;
|
||||
border: var(--color-input-border);
|
||||
background: var(--color-input);
|
||||
}
|
||||
|
||||
&.onbg .ui-input:not(:focus)
|
||||
{
|
||||
/*box-shadow: var(--shadow-short);
|
||||
background: var(--color-button-light-onbg);*/
|
||||
background: transparent;
|
||||
border: 1px dashed var(--color-line-dashed-onbg);
|
||||
}
|
||||
|
||||
.ui-searchinput-button
|
||||
{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 45px;
|
||||
text-align: center;
|
||||
font-size: var(--font-size);
|
||||
padding-top: 1px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.ui-input:focus
|
||||
{
|
||||
background-color: var(--color-input-focus-bg);
|
||||
border: var(--color-input-focus-border);
|
||||
box-shadow: var(--color-input-focus-shadow);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="ui-native-select" :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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'uiSelect',
|
||||
|
||||
props: {
|
||||
modelValue: [String, Number, Object],
|
||||
items: [Array, Function],
|
||||
entity: Object,
|
||||
disabled: Boolean,
|
||||
emptyOption: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
options: []
|
||||
}),
|
||||
|
||||
created()
|
||||
{
|
||||
this.rebuild();
|
||||
},
|
||||
|
||||
watch: {
|
||||
items: {
|
||||
deep: true,
|
||||
handler()
|
||||
{
|
||||
this.rebuild();
|
||||
this.onChange({ target: { value: this.modelValue } });
|
||||
}
|
||||
},
|
||||
entity: {
|
||||
deep: true,
|
||||
handler()
|
||||
{
|
||||
this.rebuild();
|
||||
this.onChange({ target: { value: this.modelValue } });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
rebuild()
|
||||
{
|
||||
if (this.entity && typeof this.items === 'function')
|
||||
{
|
||||
this.options = this.items(this.entity);
|
||||
}
|
||||
else if (typeof this.items !== 'function' && this.items)
|
||||
{
|
||||
this.options = [...this.items];
|
||||
}
|
||||
else
|
||||
{
|
||||
this.options = [];
|
||||
}
|
||||
},
|
||||
|
||||
onChange(ev)
|
||||
{
|
||||
this.$emit('update:modelValue', ev.target.value ? ev.target.value : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<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>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'uiToggle',
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
negative: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onContent: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
offContent: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
offWarning: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
contentLeft: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
onChange(ev)
|
||||
{
|
||||
this.$emit('update:modelValue', !this.modelValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-toggle
|
||||
{
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
height: 22px;
|
||||
|
||||
input
|
||||
{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
z-index: 2;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.is-disabled input
|
||||
{
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.is-content-left
|
||||
{
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.ui-toggle-text
|
||||
{
|
||||
margin-left: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui-toggle-text
|
||||
{
|
||||
margin-top: 1px;
|
||||
margin-left: 12px;
|
||||
|
||||
.ui-toggle.is-active &
|
||||
{
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-toggle-switch
|
||||
{
|
||||
display: inline-block;
|
||||
height: 22px;
|
||||
width: 36px;
|
||||
background: var(--color-toggle);
|
||||
border-radius: 20px;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
|
||||
i
|
||||
{
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
border-radius: 20px;
|
||||
margin: 2px;
|
||||
background: var(--color-toggle-fg);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
&.is-active
|
||||
{
|
||||
background: var(--color-toggled);
|
||||
}
|
||||
|
||||
&.is-active i
|
||||
{
|
||||
background: var(--color-toggled-fg);
|
||||
transform: translateX(14px);
|
||||
}
|
||||
|
||||
input:focus + &
|
||||
{
|
||||
border: var(--color-input-focus-border);
|
||||
box-shadow: var(--color-input-focus-shadow);
|
||||
outline: none;
|
||||
|
||||
&.is-active
|
||||
{
|
||||
background-color: var(--color-toggled);
|
||||
}
|
||||
}
|
||||
|
||||
.ui-toggle.is-disabled &
|
||||
{
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-toggle.onbg .ui-toggle-switch:not(.is-active)
|
||||
{
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
.ui-toggle.is-negative .ui-toggle-switch.is-active
|
||||
{
|
||||
background: var(--color-negative);
|
||||
border-color: transparent !important;
|
||||
|
||||
i
|
||||
{
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-toggle.is-accent .ui-toggle-switch.is-active
|
||||
{
|
||||
background: var(--color-accent);
|
||||
border-color: transparent !important;
|
||||
|
||||
i
|
||||
{
|
||||
background: var(--color-accent-fg);
|
||||
}
|
||||
}
|
||||
|
||||
.ui-toggle-off-warning
|
||||
{
|
||||
margin: 0 10px 0 -5px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user