update: toolbar popup

This commit is contained in:
yanmao 2022-01-07 00:13:10 +08:00
parent 679bb4116f
commit 614131e810
28 changed files with 272 additions and 162 deletions

View File

@ -311,84 +311,82 @@ export const lineHeightOptions: LineHeightOptions = {
};
export const toolbarOptions: ToolbarOptions = {
// popup: {
// items: [
// ['undo', 'redo'],
// {
// icon: 'text',
// items: [
// 'bold',
// 'italic',
// 'strikethrough',
// 'underline',
// 'fontsize',
// 'fontcolor',
// 'backcolor',
// 'moremark',
// ],
// },
// [
// {
// type: 'button',
// name: 'image-uploader',
// icon: 'image',
// },
// 'link',
// 'tasklist',
// 'heading',
// ],
// {
// icon: 'more',
// items: [
// {
// type: 'button',
// name: 'video-uploader',
// icon: 'video',
// },
// {
// type: 'button',
// name: 'file-uploader',
// icon: 'attachment',
// },
// {
// type: 'button',
// name: 'table',
// icon: 'table',
// },
// {
// type: 'button',
// name: 'math',
// icon: 'math',
// },
// {
// type: 'button',
// name: 'codeblock',
// icon: 'codeblock',
// },
// {
// type: 'button',
// name: 'orderedlist',
// icon: 'ordered-list',
// },
// {
// type: 'button',
// name: 'unordered-list',
// icon: 'unordered-list',
// },
// {
// type: 'button',
// name: 'hr',
// icon: 'hr',
// },
// {
// type: 'button',
// name: 'quote',
// icon: 'quote',
// },
// ],
// },
// ]
// }
popup: {
items: [
['undo', 'redo'],
{
icon: 'text',
items: [
'bold',
'italic',
'strikethrough',
'underline',
'backcolor',
'moremark',
],
},
[
{
type: 'button',
name: 'image-uploader',
icon: 'image',
},
'link',
'tasklist',
'heading',
],
{
icon: 'more',
items: [
{
type: 'button',
name: 'video-uploader',
icon: 'video',
},
{
type: 'button',
name: 'file-uploader',
icon: 'attachment',
},
{
type: 'button',
name: 'table',
icon: 'table',
},
{
type: 'button',
name: 'math',
icon: 'math',
},
{
type: 'button',
name: 'codeblock',
icon: 'codeblock',
},
{
type: 'button',
name: 'orderedlist',
icon: 'ordered-list',
},
{
type: 'button',
name: 'unordered-list',
icon: 'unordered-list',
},
{
type: 'button',
name: 'hr',
icon: 'hr',
},
{
type: 'button',
name: 'quote',
icon: 'quote',
},
],
},
],
},
};
export const pluginConfig: Record<string, PluginOptions> = {

View File

@ -111,6 +111,84 @@ export const cards: Array<CardEntry> = [
];
export const pluginConfig: { [key: string]: PluginOptions } = {
[ToolbarPlugin.pluginName]: {
popup: {
items: [
['undo', 'redo'],
{
icon: 'text',
items: [
'bold',
'italic',
'strikethrough',
'underline',
'backcolor',
'moremark',
],
},
[
{
type: 'button',
name: 'image-uploader',
icon: 'image',
},
'link',
'tasklist',
'heading',
],
{
icon: 'more',
items: [
{
type: 'button',
name: 'video-uploader',
icon: 'video',
},
{
type: 'button',
name: 'file-uploader',
icon: 'attachment',
},
{
type: 'button',
name: 'table',
icon: 'table',
},
{
type: 'button',
name: 'math',
icon: 'math',
},
{
type: 'button',
name: 'codeblock',
icon: 'codeblock',
},
{
type: 'button',
name: 'orderedlist',
icon: 'ordered-list',
},
{
type: 'button',
name: 'unordered-list',
icon: 'unordered-list',
},
{
type: 'button',
name: 'hr',
icon: 'hr',
},
{
type: 'button',
name: 'quote',
icon: 'quote',
},
],
},
],
},
},
[Italic.pluginName]: {
// 默认为 _ 下划线,这里修改为单个 * 号
markdown: '*',

View File

@ -99,7 +99,7 @@ export default defineComponent({
</script>
<style css>
.editor-toolbar .toolbar-button {
display: inline-flex;
display: inline-block;
align-items: center;
justify-content: center;
width: auto;
@ -116,12 +116,13 @@ export default defineComponent({
outline: none;
line-height: 32px;
}
.editor-toolbar.editor-toolbar-popup .toolbar-button {
min-width: 24px;
line-height: 24px;
border-radius: 4px;
margin: 0 4px;
}
.editor-toolbar:not(.editor-toolbar-mobile) .toolbar-button {
padding: 0 4px;
}

View File

@ -11,6 +11,7 @@
:title="buttonTitle"
:on-click="triggerClick"
:disabled="disabled"
:placement="placement"
>
<span v-html="buttonContent"></span>
</am-button>
@ -20,6 +21,7 @@
:title="dropdownTitle"
:on-click="toggleDropdown"
:disabled="disabled"
:placement="placement"
>
<template #icon>
<span class="colorpicker-button-dropdown-empty" />
@ -151,18 +153,21 @@ export default defineComponent({
}
.colorpicker-button-group .colorpicker-button-text {
height: 32px;
margin-right: 0;
min-width: 26px;
border-radius: 3px 0 0 3px;
}
.editor-toolbar.editor-toolbar-popup .colorpicker-button-group .colorpicker-button-text {
margin: 0;
border-radius: 3px 0 0 3px;
}
.colorpicker-button-group .colorpicker-button-text:active {
background-color: #e8e8e8;
}
.colorpicker-button-group .colorpicker-button-dropdown {
height: 32px;
margin-left: -1px;
min-width: 17px;
text-align: center;
@ -170,6 +175,15 @@ export default defineComponent({
border-radius: 0 3px 3px 0;
}
.editor-toolbar.editor-toolbar-popup .colorpicker-button-group .colorpicker-button-dropdown {
line-height: 24px;
min-width: 17px;
padding: 0 4px;
margin: 0;
margin-left: -1px;
border-radius: 0 3px 3px 0;
}
.colorpicker-button-group .colorpicker-button-dropdown:hover,
.colorpicker-button-group .colorpicker-button-dropdown:active {
background-color: #e8e8e8;

View File

@ -16,6 +16,7 @@
:title="title"
:active="visible"
:disabled="disabled"
:placement="placement"
>
<template #default>
<slot :item="content">
@ -144,11 +145,9 @@ export default defineComponent({
<style>
.toolbar-dropdown {
position: relative;
display: inline-flex;
}
.toolbar-dropdown .toolbar-dropdown-trigger {
display: inline-flex;
align-items: center;
}
@ -158,6 +157,7 @@ export default defineComponent({
.toolbar-dropdown .toolbar-dropdown-trigger-arrow .toolbar-button{
padding-right: 20px;
margin: 0;
}
.toolbar-dropdown .toolbar-dropdown-trigger-arrow .data-icon-arrow {

View File

@ -11,9 +11,9 @@
<div :class="['editor-toolbar', {'editor-toolbar-mobile': isMobile && !popup,
'editor-toolbar-popup': popup,}]" data-element="ui">
<template v-for="(item , index) in items" :key="index">
<am-button v-if="item.type === 'button'" :key="index" v-bind="item" :engine="engine" />
<am-dropdown v-if="item.type === 'dropdown'" :key="index" v-bind="item" :engine="engine" />
<am-color v-if="item.type === 'color'" :key="index" v-bind="item" :engine="engine" />
<am-button v-if="item.type === 'button'" :key="index" v-bind="item" placement="top" :engine="engine" />
<am-dropdown v-if="item.type === 'dropdown'" :key="index" v-bind="item" placement="top" :engine="engine" />
<am-color v-if="item.type === 'color'" :key="index" v-bind="item" placement="top" :engine="engine" />
<am-collapse v-if="item.type === 'collapse'" :key="index" v-bind="item" :engine="engine" />
</template>
</div>
@ -67,6 +67,8 @@ export default defineComponent({
padding: 4px 8px;
width: auto;
border-left: 1px solid #e8e8e8;
display: flex;
align-items: center;
}
.editor-toolbar .editor-toolbar-group:nth-child(1) {

View File

@ -298,6 +298,7 @@ export {
border: 0 none;
left: 0;
top: 0;
display: flex;
}
.editor-toolbar-popover {

View File

@ -33,7 +33,6 @@
overflow: auto;
}
/** ------------------- popup ---------------------- **/
/** ------------------- popup ---------------------- **/
.data-toolbar-popup-wrapper {
position: absolute;
z-index: 9999;
@ -49,7 +48,7 @@
}
.data-toolbar-popup-wrapper .editor-toolbar-popover .ant-popover-inner-content {
padding: 4px 0;
padding: 4px;
background-color: #fff;
border-radius: 4px;
border: 1px solid #dee0e3;

View File

@ -1,4 +1,4 @@
import { EngineInterface } from '@aomao/engine';
import type { EngineInterface, Placement } from '@aomao/engine';
import { ExtractPropTypes, PropType, VNode } from 'vue';
import { omit } from 'lodash';
@ -7,20 +7,6 @@ export type Command =
| { name: string; args: Array<any> }
| Array<any>
| undefined;
//tooltip 位置
export type Placement =
| 'top'
| 'left'
| 'right'
| 'bottom'
| 'topLeft'
| 'topRight'
| 'bottomLeft'
| 'bottomRight'
| 'leftTop'
| 'leftBottom'
| 'rightTop'
| 'rightBottom';
//按钮
export const buttonProps = {
engine: Object as PropType<EngineInterface | undefined>,
@ -116,6 +102,7 @@ export const dropdownProps = {
default: [],
} as const,
icon: String,
placement: String as PropType<Placement>,
content: [String, Function] as PropType<string | (() => string)>,
title: String,
disabled: {
@ -195,6 +182,7 @@ export const colorPickerProps = {
} as const,
setStroke: colorPickerGroupProps.setStroke,
onSelect: colorPickerItemProps.onSelect,
placement: String as PropType<Placement>,
};
export type ColorPickerProps = ExtractPropTypes<typeof colorPickerProps>;
//color

View File

@ -1,5 +1,5 @@
.editor-toolbar .toolbar-button {
display: inline-flex;
display: inline-block;
align-items: center;
justify-content: center;
width: auto;
@ -21,7 +21,6 @@
min-width: 24px;
line-height: 24px;
border-radius: 4px;
margin: 0 4px;
}
.editor-toolbar:not(.editor-toolbar-mobile) .toolbar-button {

View File

@ -1,7 +1,8 @@
import React, { useState } from 'react';
import Tooltip from 'antd/es/tooltip';
import classnames from 'classnames-es-ts';
import { EngineInterface, formatHotkey, isMobile } from '@aomao/engine';
import { formatHotkey, isMobile } from '@aomao/engine';
import type { EngineInterface, Placement } from '@aomao/engine';
import { autoGetHotkey } from '../utils';
import 'antd/es/tooltip/style';
import './index.css';
@ -12,19 +13,7 @@ export type ButtonProps = {
icon?: React.ReactNode;
content?: React.ReactNode | (() => React.ReactNode);
title?: string;
placement?:
| 'right'
| 'top'
| 'left'
| 'bottom'
| 'topLeft'
| 'topRight'
| 'bottomLeft'
| 'bottomRight'
| 'leftTop'
| 'leftBottom'
| 'rightTop'
| 'rightBottom';
placement?: Placement;
hotkey?: boolean | string;
command?: { name: string; args: Array<any> } | Array<any>;
autoExecute?: boolean;

View File

@ -9,7 +9,11 @@
.colorpicker-button-group .colorpicker-button-text {
margin-right: 0;
min-width: 26px;
height: 32px;
border-radius: 3px 0 0 3px;
}
.editor-toolbar.editor-toolbar-popup .colorpicker-button-group .colorpicker-button-text {
margin: 0;
border-radius: 3px 0 0 3px;
}
@ -20,12 +24,20 @@
.colorpicker-button-group .colorpicker-button-dropdown {
margin-left: -1px;
min-width: 17px;
height: 32px;
text-align: center;
padding: 0 0;
border-radius: 0 3px 3px 0;
}
.editor-toolbar.editor-toolbar-popup .colorpicker-button-group .colorpicker-button-dropdown {
line-height: 24px;
min-width: 17px;
padding: 0 4px;
margin: 0;
margin-left: -1px;
border-radius: 0 3px 3px 0;
}
.colorpicker-button-group .colorpicker-button-dropdown:hover,
.colorpicker-button-group .colorpicker-button-dropdown:active {
background-color: #e8e8e8;

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState, useRef } from 'react';
import classNames from 'classnames-es-ts';
import { EngineInterface } from '@aomao/engine';
import type { EngineInterface, Placement } from '@aomao/engine';
import { useRight } from '../hooks';
import Button from '../button';
import ColorPicker, { ColorPickerProps, Palette } from './picker';
@ -21,6 +21,7 @@ export type ColorButtonProps = {
autoExecute?: boolean;
engine?: EngineInterface;
disabled?: boolean;
placement?: Placement;
} & ColorPickerProps;
const ColorButton: React.FC<ColorButtonProps> = ({
@ -37,6 +38,7 @@ const ColorButton: React.FC<ColorButtonProps> = ({
defaultColor,
onSelect,
setStroke,
placement,
}) => {
const [pickerVisible, setPickerVisible] = useState(false);
const [buttonContent, setButtonContent] = useState(
@ -137,6 +139,7 @@ const ColorButton: React.FC<ColorButtonProps> = ({
content={buttonContent}
disabled={disabled}
onClick={(event) => triggerSelect(currentColor, event)}
placement={placement}
/>
<Button
className="colorpicker-button-dropdown toolbar-dropdown-trigger-arrow"
@ -148,6 +151,7 @@ const ColorButton: React.FC<ColorButtonProps> = ({
}
content={<span className="data-icon data-icon-arrow" />}
onClick={toggleDropdown}
placement={placement}
/>
</div>
{pickerVisible && (

View File

@ -1,10 +1,8 @@
.toolbar-dropdown {
position: relative;
display: inline-flex;
}
.toolbar-dropdown .toolbar-dropdown-trigger {
display: inline-flex;
align-items: center;
}
@ -14,6 +12,7 @@
.toolbar-dropdown .toolbar-dropdown-trigger-arrow .toolbar-button{
padding-right: 20px;
margin: 0;
}
.toolbar-dropdown .toolbar-dropdown-trigger-arrow .data-icon-arrow {
@ -64,7 +63,7 @@
color: #595959;
text-align: left;
position: relative;
display: block;
display: flex;
white-space: nowrap;
}

View File

@ -1,6 +1,6 @@
import React, { useState, useRef } from 'react';
import classnames from 'classnames-es-ts';
import { EngineInterface } from '@aomao/engine';
import type { EngineInterface, Placement } from '@aomao/engine';
import Button from '../button';
import DropdownList, { DropdownListItem } from './list';
import { useRight } from '../hooks';
@ -11,6 +11,7 @@ export type DropdownProps = {
items: Array<DropdownListItem>;
values?: string | Array<string>;
engine?: EngineInterface;
placement?: Placement;
icon?: React.ReactNode;
content?: React.ReactNode | (() => React.ReactNode);
title?: string;
@ -40,6 +41,7 @@ const Dropdown: React.FC<DropdownProps> = ({
hasArrow,
renderContent,
hasDot,
placement,
}) => {
const [visible, setVisible] = useState(false);
@ -144,6 +146,7 @@ const Dropdown: React.FC<DropdownProps> = ({
title={title}
active={visible}
disabled={disabled}
placement={placement}
/>
</div>
{visible && (

View File

@ -2,6 +2,8 @@
padding: 4px 8px;
width: auto;
border-left: 1px solid #e8e8e8;
display: flex;
align-items: center;
}
.editor-toolbar .editor-toolbar-group:nth-child(1) {

View File

@ -1,6 +1,7 @@
import React from 'react';
import classNames from 'classnames-es-ts';
import { EngineInterface, isMobile } from '@aomao/engine';
import { isMobile } from '@aomao/engine';
import type { Placement, EngineInterface } from '@aomao/engine';
import Popover from 'antd/es/popover';
import Button, { ButtonProps } from '../button';
import Dropdown, { DropdownProps } from '../dropdown';
@ -42,14 +43,26 @@ const ToolbarGroup: React.FC<GroupProps> = ({
content,
popup,
}) => {
const renderItems = () => {
const renderItems = (placement?: Placement) => {
return items.map((item, index) => {
switch (item.type) {
case 'button':
return <Button engine={engine} key={item.name} {...item} />;
return (
<Button
engine={engine}
key={item.name}
{...item}
placement={placement}
/>
);
case 'dropdown':
return (
<Dropdown engine={engine} key={item.name} {...item} />
<Dropdown
engine={engine}
key={item.name}
{...item}
placement={placement}
/>
);
case 'color':
return (
@ -57,6 +70,7 @@ const ToolbarGroup: React.FC<GroupProps> = ({
engine={engine}
key={item.name}
{...item}
placement={placement}
/>
);
case 'collapse':
@ -83,7 +97,7 @@ const ToolbarGroup: React.FC<GroupProps> = ({
})}
data-element="ui"
>
{renderItems()}
{renderItems('top')}
</div>
}
arrowPointAtCenter

View File

@ -65,6 +65,7 @@
border: 0 none;
left: 0;
top: 0;
display: flex;
}
.editor-toolbar-popover {

View File

@ -48,7 +48,7 @@
}
.data-toolbar-popup-wrapper .editor-toolbar-popover .ant-popover-inner-content {
padding: 4px 0;
padding: 4px;
background-color: #fff;
border-radius: 4px;
border: 1px solid #dee0e3;

View File

@ -1,3 +1,4 @@
import type { FileOptions } from '../types';
import {
$,
Card,
@ -71,8 +72,6 @@ export default class FileCard<V extends FileValue = FileValue> extends Card<V> {
private container?: NodeInterface;
private maxWidth: number = 752;
getLocales() {
return this.editor.language.get<{ [key: string]: string }>('file');
}
@ -83,20 +82,20 @@ export default class FileCard<V extends FileValue = FileValue> extends Card<V> {
};
onWindowResize = () => {
this.maxWidth = this.getMaxWidth();
this.updateMaxWidth();
};
updateMaxWidth = () => {
const maxWidth = this.getMaxWidth();
this.root
.find('.data-file-title')
.css('max-width', this.maxWidth - 100 + 'px');
.css('max-width', maxWidth - 100 + 'px');
};
onBeforeRender = (action: 'preview' | 'download', url: string) => {
const filePlugin = this.editor.plugin.components['file'];
const filePlugin = this.editor.plugin.findPlugin<FileOptions>('file');
if (filePlugin) {
const { onBeforeRender } = (filePlugin['options'] || {}) as any;
const { onBeforeRender } = filePlugin.options || {};
if (onBeforeRender) return onBeforeRender(action, url);
}
return url;
@ -259,7 +258,6 @@ export default class FileCard<V extends FileValue = FileValue> extends Card<V> {
this.bindErrorEvent(this.root);
}
this.container?.find('.percent').html(`${value.percent}%`);
this.maxWidth = this.getMaxWidth();
this.updateMaxWidth();
window.addEventListener('resize', this.onWindowResize);
}

View File

@ -19,8 +19,7 @@ import type { FileValue } from './component';
import FileUploader from './uploader';
import type { FileUploaderOptions } from './uploader';
import locales from './locales';
export interface FileOptions extends PluginOptions {}
import { FileOptions } from './types';
export default class<T extends FileOptions = FileOptions> extends Plugin<T> {
static get pluginName() {

View File

@ -0,0 +1,5 @@
import { PluginOptions } from '@aomao/engine';
export interface FileOptions extends PluginOptions {
onBeforeRender?: (action: 'preview' | 'download', url: string) => string;
}

View File

@ -1,3 +1,4 @@
import type { ImageOptions } from '@/types';
import {
Card,
CardToolbarItemOptions,
@ -262,10 +263,10 @@ class ImageComponent<T extends ImageValue = ImageValue> extends Card<T> {
percent: value.percent,
message: value.message,
onBeforeRender: (status, src) => {
const imagePlugin = this.editor.plugin.components['image'];
const imagePlugin =
this.editor.plugin.findPlugin<ImageOptions>('image');
if (imagePlugin) {
const { onBeforeRender } = (imagePlugin['options'] ||
{}) as any;
const { onBeforeRender } = imagePlugin.options || {};
if (onBeforeRender) return onBeforeRender(status, src);
}
return src;

View File

@ -1,6 +1,5 @@
import {
$,
CardEntry,
CardInterface,
CardType,
CARD_KEY,
@ -10,17 +9,13 @@ import {
NodeInterface,
Plugin,
PluginEntry,
PluginOptions,
READY_CARD_KEY,
} from '@aomao/engine';
import ImageComponent, { ImageValue } from './component';
import ImageUploader from './uploader';
import { ImageUploaderOptions } from './uploader';
import locales from './locales';
export interface ImageOptions extends PluginOptions {
onBeforeRender?: (status: 'uploading' | 'done', src: string) => string;
}
import { ImageOptions } from './types';
export default class<T extends ImageOptions = ImageOptions> extends Plugin<T> {
static get pluginName() {

View File

@ -1,4 +1,4 @@
import { NodeInterface } from '@aomao/engine';
import { NodeInterface, PluginOptions } from '@aomao/engine';
import { EventEmitter2 } from 'eventemitter2';
export interface PswpInterface extends EventEmitter2 {
@ -27,3 +27,7 @@ export interface PswpInterface extends EventEmitter2 {
close(): void;
destroy(): void;
}
export interface ImageOptions extends PluginOptions {
onBeforeRender?: (status: 'uploading' | 'done', src: string) => string;
}

View File

@ -1,3 +1,4 @@
import { VideoOptions } from '@/types';
import type {
CardToolbarItemOptions,
ToolbarItemOptions,
@ -190,9 +191,10 @@ class VideoComponent<T extends VideoValue = VideoValue> extends Card<T> {
}
onBeforeRender = (action: 'query' | 'download' | 'cover', url: string) => {
const videoPlugin = this.editor.plugin.components['video'];
const videoPlugin =
this.editor.plugin.findPlugin<VideoOptions>('video');
if (videoPlugin) {
const { onBeforeRender } = (videoPlugin['options'] || {}) as any;
const { onBeforeRender } = videoPlugin.options || {};
if (onBeforeRender) return onBeforeRender(action, url);
}
return url;

View File

@ -20,14 +20,7 @@ import type { VideoValue, VideoStatus } from './component';
import VideoUploader from './uploader';
import type { VideoUploaderOptions } from './uploader';
import locales from './locales';
export interface VideoOptions extends PluginOptions {
onBeforeRender?: (
action: 'download' | 'query' | 'cover',
url: string,
) => string;
showTitle?: boolean;
}
import { VideoOptions } from './types';
export default class VideoPlugin<
T extends VideoOptions = VideoOptions,

View File

@ -0,0 +1,9 @@
import { PluginOptions } from '@aomao/engine';
export interface VideoOptions extends PluginOptions {
onBeforeRender?: (
action: 'download' | 'query' | 'cover',
url: string,
) => string;
showTitle?: boolean;
}