parent
c46845d865
commit
f84824aba8
|
@ -72,7 +72,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
> span {
|
> span {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-left: 40px;
|
margin-left: 24px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: @c-text;
|
color: @c-text;
|
||||||
height: @s-nav-height;
|
height: @s-nav-height;
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
+ *:not(a) {
|
+ *:not(a) {
|
||||||
margin-left: 40px;
|
margin-left: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// second nav
|
// second nav
|
||||||
|
|
|
@ -23,3 +23,5 @@
|
||||||
# log
|
# log
|
||||||
*.log
|
*.log
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
**/ffmpeg
|
|
@ -31,47 +31,7 @@ new Engine(...,{
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
`defaultData`: Default drop-down query list display data
|
|
||||||
|
|
||||||
`onSearch`: the method to query, or configure the action, choose one of the two
|
|
||||||
|
|
||||||
`onSelect`: Call back after selecting an item in the list, here you can return a custom value combined with key and name to form a new value and store it in cardValue. And it will return together after executing the getList command
|
|
||||||
|
|
||||||
`onClick`: Triggered when clicking on the "mention"
|
|
||||||
|
|
||||||
`onMouseEnter`: Triggered when the mouse moves over the "mention"
|
|
||||||
|
|
||||||
`onRender`: custom rendering list
|
|
||||||
|
|
||||||
`onRenderItem`: custom rendering list item
|
|
||||||
|
|
||||||
`onLoading`: custom rendering loading status
|
|
||||||
|
|
||||||
`onEmpty`: custom render empty state
|
|
||||||
|
|
||||||
`action`: query address, always use `GET` request, parameter `keyword`
|
|
||||||
|
|
||||||
`data`: When querying, these data will be sent to the server at the same time
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
//List data displayed by default
|
|
||||||
defaultData?: Array<{ key: string, name: string, avatar?: string}>
|
|
||||||
//Method for query, or configure action, choose one of the two
|
|
||||||
onSearch?:(keyword: string) => Promise<Array<{ key: string, name: string, avatar?: string}>>
|
|
||||||
//Call back after selecting an item in the list, here you can return a custom value combined with key and name to form a new value and store it in cardValue. And it will return together after executing the getList command
|
|
||||||
onSelect?: (data: {[key:string]: string}) => void | {[key: string]: string}
|
|
||||||
//Click event on "mention"
|
|
||||||
onClick?:(data: {[key:string]: string}) => void
|
|
||||||
// Triggered when the mouse moves over the "mention"
|
|
||||||
onMouseEnter?:(node: NodeInterface, data: {[key:string]: string}) => void
|
|
||||||
//Customize the rendering list, bindItem can bind the required properties and events for the list item
|
|
||||||
onRender?: (data: MentionItem, root: NodeInterface, bindItem: (node: NodeInterface, data: {[key:string]: string}) => NodeInterface) => Promise<string | NodeInterface | void>;
|
|
||||||
//Custom rendering list items
|
|
||||||
onRenderItem?: (item: MentionItem, root: NodeInterface) => string | NodeInterface | void
|
|
||||||
// Customize the rendering loading status
|
|
||||||
onLoading?: (root: NodeInterface) => string | NodeInterface | void
|
|
||||||
// Custom render empty state
|
|
||||||
onEmpty?: (root: NodeInterface) => string | NodeInterface | void
|
|
||||||
/**
|
/**
|
||||||
* look for the address
|
* look for the address
|
||||||
*/
|
*/
|
||||||
|
@ -124,3 +84,136 @@ Get all mentions in the document
|
||||||
//Return Array<{ key: string, name: string}>
|
//Return Array<{ key: string, name: string}>
|
||||||
engine.command.executeMethod('mention', 'getList');
|
engine.command.executeMethod('mention', 'getList');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Plug-in events
|
||||||
|
|
||||||
|
`mention:default`: default drop-down query list to display data
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:default', () => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:search`: Method of query, or configure action, choose one of the two
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:search', (keyword) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
query({ keyword })
|
||||||
|
.then((result) => {
|
||||||
|
resolve(result);
|
||||||
|
})
|
||||||
|
.catch(() => resolve([]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:select`: Call back after selecting an item in the list, here you can return a custom value combined with key and name to form a new value and store it in cardValue. And will return together after executing the getList command
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:select', (data) => {
|
||||||
|
data['test'] = 'test';
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:item-click`: triggered when clicking on "mention"
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on(
|
||||||
|
'mention:item-click',
|
||||||
|
(root: NodeInterface, { key, name }: { key: string; name: string }) => {
|
||||||
|
console.log('mention click:', key, '-', name);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:enter`: Triggered when the mouse moves over the "mention"
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on(
|
||||||
|
'mention:enter',
|
||||||
|
(layout: NodeInterface, { name }: { key: string; name: string }) => {
|
||||||
|
ReactDOM.render(
|
||||||
|
<div style={{ padding: 5 }}>
|
||||||
|
<p>This is name: {name}</p>
|
||||||
|
<p>Configure the mention:enter event of the mention plugin</p>
|
||||||
|
<p>Use ReactDOM.render to customize rendering here</p>
|
||||||
|
<p>Use ReactDOM.render to customize rendering here</p>
|
||||||
|
</div>,
|
||||||
|
layout.get<HTMLElement>()!,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:render`: custom rendering list
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on(
|
||||||
|
'mention:render',
|
||||||
|
(
|
||||||
|
root: NodeInterface,
|
||||||
|
data: Array<MentionItem>,
|
||||||
|
bindItem: (
|
||||||
|
node: NodeInterface,
|
||||||
|
data: { [key: string]: string },
|
||||||
|
) => NodeInterface,
|
||||||
|
) => {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
const renderCallback = (items: { [key: string]: Element }) => {
|
||||||
|
// Traverse the DOM node of each item
|
||||||
|
Object.keys(items).forEach((key) => {
|
||||||
|
const element = items[key];
|
||||||
|
const item = data.find((d) => d.key === key);
|
||||||
|
if (!item) return;
|
||||||
|
// Bind the attributes and events of each list item to meet the functional needs of the up, down, left, and right selection in the editor
|
||||||
|
bindItem($(element), item);
|
||||||
|
});
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
ReactDOM.render(
|
||||||
|
<MentionList data={data} callback={renderCallback} />,
|
||||||
|
root.get<HTMLElement>()!,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:render-item`: custom rendering list item
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:render-item', (data, root) => {
|
||||||
|
const item = $(`<div>${data}</div>`);
|
||||||
|
root.append(item);
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:loading`: custom rendering loading status
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:loading', (data, root) => {
|
||||||
|
root.html(`<div>${data}</div>`);
|
||||||
|
// or
|
||||||
|
ReactDOM.render(
|
||||||
|
<div className="data-mention-loading">Loading...</div>,
|
||||||
|
root.get<HTMLElement>()!,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:empty`: custom render empty state
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:empty', (root) => {
|
||||||
|
root.html('<div>No data found</div>');
|
||||||
|
// or
|
||||||
|
ReactDOM.render(
|
||||||
|
<div className="data-mention-empty">Empty</div>,
|
||||||
|
root.get<HTMLElement>()!,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
|
@ -31,49 +31,9 @@ new Engine(...,{
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
`defaultData`: 默认下拉查询列表展示数据
|
|
||||||
|
|
||||||
`onSearch`: 查询时的方法,或者配置 action,二选其一
|
|
||||||
|
|
||||||
`onSelect`: 选中列表中的一项后回调,这里可以返回一个自定义值与 key、name 一起组合成新的值存在 cardValue 里面。并且执行 getList 命令后会一起返回来
|
|
||||||
|
|
||||||
`onClick`: 在“提及”上单击时触发
|
|
||||||
|
|
||||||
`onMouseEnter`: 鼠标移入“提及”上时触发
|
|
||||||
|
|
||||||
`onRender`: 自定义渲染列表
|
|
||||||
|
|
||||||
`onRenderItem`: 自定义渲染列表项
|
|
||||||
|
|
||||||
`onLoading`: 自定渲染加载状态
|
|
||||||
|
|
||||||
`onEmpty`: 自定渲染空状态
|
|
||||||
|
|
||||||
`action`: 查询地址,始终使用 `GET` 请求,参数 `keyword`
|
|
||||||
|
|
||||||
`data`: 查询时同时将这些数据一起传到到服务端
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
//默认展示的列表数据
|
|
||||||
defaultData?: Array<{ key: string, name: string, avatar?: string}>
|
|
||||||
//查询时的方法,或者配置 action,二选其一
|
|
||||||
onSearch?:(keyword: string) => Promise<Array<{ key: string, name: string, avatar?: string}>>
|
|
||||||
//选中列表中的一项后回调,这里可以返回一个自定义值与 key、name 一起组合成新的值存在 cardValue 里面。并且执行 getList 命令后会一起返回来
|
|
||||||
onSelect?: (data: {[key:string]: string}) => void | {[key: string]: string}
|
|
||||||
//在“提及”上单击事件
|
|
||||||
onClick?:(data: {[key:string]: string}) => void
|
|
||||||
//鼠标移入“提及”上时触发
|
|
||||||
onMouseEnter?:(node: NodeInterface, data: {[key:string]: string}) => void
|
|
||||||
//自定义渲染列表,bindItem 可以为列表项绑定需要的属性和事件
|
|
||||||
onRender?: (data: MentionItem, root: NodeInterface, bindItem: (node: NodeInterface, data: {[key:string]: string}) => NodeInterface) => Promise<string | NodeInterface | void>;
|
|
||||||
//自定义渲染列表项
|
|
||||||
onRenderItem?: (item: MentionItem, root: NodeInterface) => string | NodeInterface | void
|
|
||||||
// 自定渲染加载状态
|
|
||||||
onLoading?: (root: NodeInterface) => string | NodeInterface | void
|
|
||||||
// 自定渲染空状态
|
|
||||||
onEmpty?: (root: NodeInterface) => string | NodeInterface | void
|
|
||||||
/**
|
/**
|
||||||
* 查询地址
|
* 查询地址,或者监听 mention:search 事件执行查询
|
||||||
*/
|
*/
|
||||||
action?: string;
|
action?: string;
|
||||||
/**
|
/**
|
||||||
|
@ -124,3 +84,136 @@ parse?: (
|
||||||
//返回 Array<{ key: string, name: string}>
|
//返回 Array<{ key: string, name: string}>
|
||||||
engine.command.executeMethod('mention', 'getList');
|
engine.command.executeMethod('mention', 'getList');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 插件事件
|
||||||
|
|
||||||
|
`mention:default`: 默认下拉查询列表展示数据
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:default', () => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:search`: 查询时的方法,或者配置 action,二选其一
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:search', (keyword) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
query({ keyword })
|
||||||
|
.then((result) => {
|
||||||
|
resolve(result);
|
||||||
|
})
|
||||||
|
.catch(() => resolve([]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:select`: 选中列表中的一项后回调,这里可以返回一个自定义值与 key、name 一起组合成新的值存在 cardValue 里面。并且执行 getList 命令后会一起返回来
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:select', (data) => {
|
||||||
|
data['test'] = 'test';
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:item-click`: 在“提及”上单击时触发
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on(
|
||||||
|
'mention:item-click',
|
||||||
|
(root: NodeInterface, { key, name }: { key: string; name: string }) => {
|
||||||
|
console.log('mention click:', key, '-', name);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:enter`: 鼠标移入“提及”上时触发
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on(
|
||||||
|
'mention:enter',
|
||||||
|
(layout: NodeInterface, { name }: { key: string; name: string }) => {
|
||||||
|
ReactDOM.render(
|
||||||
|
<div style={{ padding: 5 }}>
|
||||||
|
<p>This is name: {name}</p>
|
||||||
|
<p>配置 mention 插件的 mention:enter 事件</p>
|
||||||
|
<p>此处使用 ReactDOM.render 自定义渲染</p>
|
||||||
|
<p>Use ReactDOM.render to customize rendering here</p>
|
||||||
|
</div>,
|
||||||
|
layout.get<HTMLElement>()!,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:render`: 自定义渲染列表
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on(
|
||||||
|
'mention:render',
|
||||||
|
(
|
||||||
|
root: NodeInterface,
|
||||||
|
data: Array<MentionItem>,
|
||||||
|
bindItem: (
|
||||||
|
node: NodeInterface,
|
||||||
|
data: { [key: string]: string },
|
||||||
|
) => NodeInterface,
|
||||||
|
) => {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
const renderCallback = (items: { [key: string]: Element }) => {
|
||||||
|
// 遍历每个项的DOM节点
|
||||||
|
Object.keys(items).forEach((key) => {
|
||||||
|
const element = items[key];
|
||||||
|
const item = data.find((d) => d.key === key);
|
||||||
|
if (!item) return;
|
||||||
|
// 绑定每个列表项所属的属性、事件,以满足编辑器中上下左右选择的功能需要
|
||||||
|
bindItem($(element), item);
|
||||||
|
});
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
ReactDOM.render(
|
||||||
|
<MentionList data={data} callback={renderCallback} />,
|
||||||
|
root.get<HTMLElement>()!,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:render-item`: 自定义渲染列表项
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:render-item', (data, root) => {
|
||||||
|
const item = $(`<div>${data}</div>`);
|
||||||
|
root.append(item);
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:loading`: 自定渲染加载状态
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:loading', (data, root) => {
|
||||||
|
root.html(`<div>${data}</div>`);
|
||||||
|
// or
|
||||||
|
ReactDOM.render(
|
||||||
|
<div className="data-mention-loading">Loading...</div>,
|
||||||
|
root.get<HTMLElement>()!,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:empty`: 自定渲染空状态
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:empty', (root) => {
|
||||||
|
root.html('<div>没有查询到数据</div>');
|
||||||
|
// or
|
||||||
|
ReactDOM.render(
|
||||||
|
<div className="data-mention-empty">Empty</div>,
|
||||||
|
root.get<HTMLElement>()!,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
|
@ -41,6 +41,17 @@ new Engine(...,{
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Overflow display
|
||||||
|
|
||||||
|
```ts
|
||||||
|
overflow?: {
|
||||||
|
// Relative to the maximum displayable width on the left side of the editor
|
||||||
|
maxLeftWidth?: () => number;
|
||||||
|
// Relative to the maximum displayable width on the right side of the editor
|
||||||
|
maxRightWidth?: () => number;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
## Command
|
## Command
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|
|
@ -41,6 +41,17 @@ new Engine(...,{
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 溢出展示
|
||||||
|
|
||||||
|
```ts
|
||||||
|
overflow?: {
|
||||||
|
// 相对编辑器左侧最大能展示的宽度
|
||||||
|
maxLeftWidth?: () => number;
|
||||||
|
// 相对于编辑器右侧最大能展示的宽度
|
||||||
|
maxRightWidth?: () => number;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
## 命令
|
## 命令
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|
|
@ -40,6 +40,14 @@ new Engine(...,{
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Whether to display the video title
|
||||||
|
|
||||||
|
Default Display
|
||||||
|
|
||||||
|
```ts
|
||||||
|
showTitle?: boolean
|
||||||
|
```
|
||||||
|
|
||||||
### File Upload
|
### File Upload
|
||||||
|
|
||||||
`action`: upload address, always use `POST` request
|
`action`: upload address, always use `POST` request
|
||||||
|
|
|
@ -40,6 +40,14 @@ new Engine(...,{
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 是否显示视频标题
|
||||||
|
|
||||||
|
默认显示
|
||||||
|
|
||||||
|
```ts
|
||||||
|
showTitle?: boolean
|
||||||
|
```
|
||||||
|
|
||||||
### 文件上传
|
### 文件上传
|
||||||
|
|
||||||
`action`: 上传地址,始终使用 `POST` 请求
|
`action`: 上传地址,始终使用 `POST` 请求
|
||||||
|
|
|
@ -44,9 +44,9 @@
|
||||||
.doc-comment-layer {
|
.doc-comment-layer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
width: calc(50% - 454px);
|
padding-left: 16px;
|
||||||
left: calc(50% + 428px);
|
min-width: 260px;
|
||||||
padding-left: 24px;
|
right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.doc-comment-title {
|
.doc-comment-title {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
CardEntry,
|
CardEntry,
|
||||||
PluginOptions,
|
PluginOptions,
|
||||||
NodeInterface,
|
NodeInterface,
|
||||||
|
$,
|
||||||
} from '@aomao/engine';
|
} from '@aomao/engine';
|
||||||
//引入插件 begin
|
//引入插件 begin
|
||||||
import Redo from '@aomao/plugin-redo';
|
import Redo from '@aomao/plugin-redo';
|
||||||
|
@ -43,7 +44,6 @@ import LineHeight from '@aomao/plugin-line-height';
|
||||||
import Mention, { MentionComponent } from '@aomao/plugin-mention';
|
import Mention, { MentionComponent } from '@aomao/plugin-mention';
|
||||||
import Embed, { EmbedComponent } from '@aomao/plugin-embed';
|
import Embed, { EmbedComponent } from '@aomao/plugin-embed';
|
||||||
import Test, { TestComponent } from './plugins/test';
|
import Test, { TestComponent } from './plugins/test';
|
||||||
//import Mind, { MindComponent } from '@aomao/plugin-mind';
|
|
||||||
import {
|
import {
|
||||||
ToolbarPlugin,
|
ToolbarPlugin,
|
||||||
ToolbarComponent,
|
ToolbarComponent,
|
||||||
|
@ -98,7 +98,6 @@ export const plugins: Array<PluginEntry> = [
|
||||||
Mention,
|
Mention,
|
||||||
Embed,
|
Embed,
|
||||||
Test,
|
Test,
|
||||||
//Mind
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const cards: Array<CardEntry> = [
|
export const cards: Array<CardEntry> = [
|
||||||
|
@ -115,10 +114,35 @@ export const cards: Array<CardEntry> = [
|
||||||
MentionComponent,
|
MentionComponent,
|
||||||
TestComponent,
|
TestComponent,
|
||||||
EmbedComponent,
|
EmbedComponent,
|
||||||
//MindComponent
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const pluginConfig: { [key: string]: PluginOptions } = {
|
export const pluginConfig: { [key: string]: PluginOptions } = {
|
||||||
|
[Table.pluginName]: {
|
||||||
|
overflow: {
|
||||||
|
maxLeftWidth: () => {
|
||||||
|
// 编辑区域位置
|
||||||
|
const rect = $('.editor-content')
|
||||||
|
.get<HTMLElement>()
|
||||||
|
?.getBoundingClientRect();
|
||||||
|
const editorLeft = rect?.left || 0;
|
||||||
|
// 减去大纲的宽度
|
||||||
|
const width = editorLeft - $('.data-toc-wrapper').width();
|
||||||
|
// 留 16px 的间隔
|
||||||
|
return width <= 0 ? 100 : width - 16;
|
||||||
|
},
|
||||||
|
maxRightWidth: () => {
|
||||||
|
// 编辑区域位置
|
||||||
|
const rect = $('.editor-content')
|
||||||
|
.get<HTMLElement>()
|
||||||
|
?.getBoundingClientRect();
|
||||||
|
const editorRigth = (rect?.right || 0) - (rect?.width || 0);
|
||||||
|
// 减去评论区域的宽度
|
||||||
|
const width = editorRigth - $('.doc-comment-layer').width();
|
||||||
|
// 留 16px 的间隔
|
||||||
|
return width <= 0 ? 100 : width - 16;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
[MarkRange.pluginName]: {
|
[MarkRange.pluginName]: {
|
||||||
//标记类型集合
|
//标记类型集合
|
||||||
keys: ['comment'],
|
keys: ['comment'],
|
||||||
|
@ -153,7 +177,7 @@ export const pluginConfig: { [key: string]: PluginOptions } = {
|
||||||
},
|
},
|
||||||
[Video.pluginName]: {
|
[Video.pluginName]: {
|
||||||
onBeforeRender: (status: string, url: string) => {
|
onBeforeRender: (status: string, url: string) => {
|
||||||
return url + `?token=12323`;
|
return url;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[Math.pluginName]: {
|
[Math.pluginName]: {
|
||||||
|
|
|
@ -90,8 +90,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-container {
|
.editor-container {
|
||||||
background: #fafafa;
|
|
||||||
background-color: #fafafa;
|
|
||||||
padding: 24px 0 64px;
|
padding: 24px 0 64px;
|
||||||
height: calc(100vh - 138px);
|
height: calc(100vh - 138px);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -111,7 +109,6 @@
|
||||||
width: 812px;
|
width: 812px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #f0f0f0;
|
|
||||||
min-height: 800px;
|
min-height: 800px;
|
||||||
@media @mobile {
|
@media @mobile {
|
||||||
width: auto;
|
width: auto;
|
||||||
|
@ -121,7 +118,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-content .am-engine {
|
.editor-content .am-engine {
|
||||||
padding: 40px 60px 60px;
|
padding: 40px 0 60px;
|
||||||
|
|
||||||
@media @mobile {
|
@media @mobile {
|
||||||
padding: 18px 0 0 0;
|
padding: 18px 0 0 0;
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
.data-toc-wrapper {
|
.data-toc-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
width: calc(50% - 454px);
|
min-width: 210px;
|
||||||
right: calc(50% + 428px);
|
padding: 0 16px;
|
||||||
padding-right: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-toc-title {
|
.data-toc-title {
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
.doc-editor-mode {
|
|
||||||
font-size: 12px;
|
|
||||||
background: #ffffff;
|
|
||||||
padding: 0;
|
|
||||||
z-index: 9999;
|
|
||||||
position: fixed;
|
|
||||||
left: 10px;
|
|
||||||
top: 68px;
|
|
||||||
}
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
.doc-editor-mode {
|
||||||
|
font-size: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
position: fixed;
|
||||||
|
left: 10px;
|
||||||
|
top: 68px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
.am-engine h1,
|
||||||
|
.am-engine h2,
|
||||||
|
.am-engine h3,
|
||||||
|
.am-engine h4,
|
||||||
|
.am-engine h5,
|
||||||
|
.am-engine h6 {
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.am-engine > [data-id],
|
||||||
|
.am-engine > div[data-card-key] {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.am-engine table,
|
||||||
|
.am-engine tbody,
|
||||||
|
.am-engine tr,
|
||||||
|
.am-engine td {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.am-engine > ul > li {
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import Space from 'antd/es/space';
|
||||||
import Button from 'antd/es/button';
|
import Button from 'antd/es/button';
|
||||||
import 'antd/es/space/style';
|
import 'antd/es/space/style';
|
||||||
import 'antd/es/button/style';
|
import 'antd/es/button/style';
|
||||||
import './editor.css';
|
import './editor.less';
|
||||||
|
|
||||||
const localMember =
|
const localMember =
|
||||||
typeof localStorage === 'undefined' ? null : localStorage.getItem('member');
|
typeof localStorage === 'undefined' ? null : localStorage.getItem('member');
|
||||||
|
@ -72,7 +72,7 @@ export default () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Context.Provider value={{ lang }}>
|
<Context.Provider value={{ lang }}>
|
||||||
<Space className="doc-editor-mode">
|
{/* <Space className="doc-editor-mode">
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
disabled={readonly}
|
disabled={readonly}
|
||||||
|
@ -89,7 +89,7 @@ export default () => {
|
||||||
>
|
>
|
||||||
{lang === 'zh-CN' ? '编辑模式' : 'Edit mode'}
|
{lang === 'zh-CN' ? '编辑模式' : 'Edit mode'}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space> */}
|
||||||
<Editor
|
<Editor
|
||||||
lang={lang}
|
lang={lang}
|
||||||
placeholder="这里是编辑区域哦~"
|
placeholder="这里是编辑区域哦~"
|
||||||
|
|
|
@ -52,7 +52,7 @@ function startServer() {
|
||||||
if (action === 'ready') {
|
if (action === 'ready') {
|
||||||
client.add(ws, data.doc_id, {
|
client.add(ws, data.doc_id, {
|
||||||
id: getId(data.doc_id, uid),
|
id: getId(data.doc_id, uid),
|
||||||
name: `Guest-${uid}`,
|
name: `G-${uid}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
|
|
@ -322,6 +322,7 @@ abstract class CardEntry<T extends CardValue = {}> implements CardInterface {
|
||||||
const className = 'card-selected-other';
|
const className = 'card-selected-other';
|
||||||
if (selected) this.root.addClass(className);
|
if (selected) this.root.addClass(className);
|
||||||
else this.root.removeClass(className);
|
else this.root.removeClass(className);
|
||||||
|
return center;
|
||||||
}
|
}
|
||||||
onActivate(activated: boolean) {
|
onActivate(activated: boolean) {
|
||||||
if (!this.resize) return;
|
if (!this.resize) return;
|
||||||
|
@ -335,7 +336,7 @@ abstract class CardEntry<T extends CardValue = {}> implements CardInterface {
|
||||||
rgb: string;
|
rgb: string;
|
||||||
},
|
},
|
||||||
): NodeInterface | void {
|
): NodeInterface | void {
|
||||||
this.onSelectByOther(activated, value);
|
return this.onSelectByOther(activated, value);
|
||||||
}
|
}
|
||||||
onChange?(trigger: 'remote' | 'local', node: NodeInterface): void;
|
onChange?(trigger: 'remote' | 'local', node: NodeInterface): void;
|
||||||
destroy() {
|
destroy() {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { DATA_ELEMENT, TRIGGER_CARD_ID, UI } from '../../constants';
|
||||||
import { $ } from '../../node';
|
import { $ } from '../../node';
|
||||||
import { isEngine, isMobile } from '../../utils';
|
import { isEngine, isMobile } from '../../utils';
|
||||||
import Position from '../../position';
|
import Position from '../../position';
|
||||||
|
import placements from '../../position/placements';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
export const isCardToolbarItemOptions = (
|
export const isCardToolbarItemOptions = (
|
||||||
|
@ -30,6 +31,7 @@ class CardToolbar implements CardToolbarInterface {
|
||||||
private position: Position;
|
private position: Position;
|
||||||
#hideTimeout: NodeJS.Timeout | null = null;
|
#hideTimeout: NodeJS.Timeout | null = null;
|
||||||
#showTimeout: NodeJS.Timeout | null = null;
|
#showTimeout: NodeJS.Timeout | null = null;
|
||||||
|
#defaultAlign: keyof typeof placements = 'topLeft';
|
||||||
|
|
||||||
constructor(editor: EditorInterface, card: CardInterface) {
|
constructor(editor: EditorInterface, card: CardInterface) {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
|
@ -41,6 +43,10 @@ class CardToolbar implements CardToolbarInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDefaultAlign(align: keyof typeof placements) {
|
||||||
|
this.#defaultAlign = align;
|
||||||
|
}
|
||||||
|
|
||||||
clearHide = () => {
|
clearHide = () => {
|
||||||
if (this.#hideTimeout) clearTimeout(this.#hideTimeout);
|
if (this.#hideTimeout) clearTimeout(this.#hideTimeout);
|
||||||
this.#hideTimeout = null;
|
this.#hideTimeout = null;
|
||||||
|
@ -287,14 +293,14 @@ class CardToolbar implements CardToolbarInterface {
|
||||||
(this.card.constructor as CardEntry).cardName,
|
(this.card.constructor as CardEntry).cardName,
|
||||||
);
|
);
|
||||||
if (this.toolbar) this.toolbar.show();
|
if (this.toolbar) this.toolbar.show();
|
||||||
let prevAlign = 'topLeft';
|
let prevAlign = this.#defaultAlign;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.position.bind(
|
this.position.bind(
|
||||||
container,
|
container,
|
||||||
this.card.isMaximize
|
this.card.isMaximize
|
||||||
? this.card.getCenter().first()!
|
? this.card.getCenter().first()!
|
||||||
: this.card.root,
|
: this.card.root,
|
||||||
'topLeft',
|
this.#defaultAlign,
|
||||||
this.offset,
|
this.offset,
|
||||||
(rect) => {
|
(rect) => {
|
||||||
if (
|
if (
|
||||||
|
@ -311,7 +317,7 @@ class CardToolbar implements CardToolbarInterface {
|
||||||
this.position.update(false);
|
this.position.update(false);
|
||||||
} else if (
|
} else if (
|
||||||
this.offset &&
|
this.offset &&
|
||||||
rect.align === 'topLeft' &&
|
rect.align === this.#defaultAlign &&
|
||||||
rect.align !== prevAlign
|
rect.align !== prevAlign
|
||||||
) {
|
) {
|
||||||
this.position.setOffset(this.offset);
|
this.position.setOffset(this.offset);
|
||||||
|
|
|
@ -441,6 +441,7 @@ class ChangeModel implements ChangeInterface {
|
||||||
let node: NodeInterface | null = $(childNodes[0]);
|
let node: NodeInterface | null = $(childNodes[0]);
|
||||||
let prev: NodeInterface | null = null;
|
let prev: NodeInterface | null = null;
|
||||||
const appendNodes = [];
|
const appendNodes = [];
|
||||||
|
let startRangeNodeParent = startRange.node.parent();
|
||||||
while (node && node.length > 0) {
|
while (node && node.length > 0) {
|
||||||
nodeApi.removeSide(node);
|
nodeApi.removeSide(node);
|
||||||
const next: NodeInterface | null = node.next();
|
const next: NodeInterface | null = node.next();
|
||||||
|
@ -458,6 +459,22 @@ class ChangeModel implements ChangeInterface {
|
||||||
if (!next) {
|
if (!next) {
|
||||||
range.select(node, true).collapse(false);
|
range.select(node, true).collapse(false);
|
||||||
}
|
}
|
||||||
|
// 被删除了重新设置开始节点位置
|
||||||
|
if (
|
||||||
|
startRange &&
|
||||||
|
(!startRangeNodeParent || startRangeNodeParent.length === 0)
|
||||||
|
) {
|
||||||
|
const children = node.children();
|
||||||
|
startRangeNodeParent = node.parent();
|
||||||
|
startRange = {
|
||||||
|
node: node,
|
||||||
|
offset:
|
||||||
|
children.length === 1 &&
|
||||||
|
children[0].nodeName === 'BR'
|
||||||
|
? 0
|
||||||
|
: range.startOffset,
|
||||||
|
};
|
||||||
|
}
|
||||||
node = next;
|
node = next;
|
||||||
}
|
}
|
||||||
if (mergeNode[0]) {
|
if (mergeNode[0]) {
|
||||||
|
|
|
@ -490,11 +490,11 @@ class NativeEvent {
|
||||||
return;
|
return;
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
change.cacheRangeBeforeCommand();
|
change.cacheRangeBeforeCommand();
|
||||||
|
this.paste(source);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// 如果 text 和 html 都有,就解析 text
|
// 如果 text 和 html 都有,就解析 text
|
||||||
pasteMarkdown(source, text || '');
|
pasteMarkdown(source, text || '');
|
||||||
}, 200);
|
}, 200);
|
||||||
this.paste(source);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import Request, {
|
||||||
import Scrollbar from './scrollbar';
|
import Scrollbar from './scrollbar';
|
||||||
import Position from './position';
|
import Position from './position';
|
||||||
import { $, getHashId, uuid } from './node';
|
import { $, getHashId, uuid } from './node';
|
||||||
|
import Resizer from './resizer';
|
||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
@ -69,4 +70,5 @@ export {
|
||||||
isRangeInterface,
|
isRangeInterface,
|
||||||
isRange,
|
isRange,
|
||||||
isSelection,
|
isSelection,
|
||||||
|
Resizer,
|
||||||
};
|
};
|
||||||
|
|
|
@ -105,12 +105,6 @@ class RangeColoring implements RangeColoringInterface {
|
||||||
child = $(
|
child = $(
|
||||||
`<div class="${USER_BACKGROUND_CLASS}" ${DATA_UUID}="${uuid}" ${DATA_COLOR}="${color}" />`,
|
`<div class="${USER_BACKGROUND_CLASS}" ${DATA_UUID}="${uuid}" ${DATA_COLOR}="${color}" />`,
|
||||||
);
|
);
|
||||||
child.css({
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
'pointer-events': 'none',
|
|
||||||
});
|
|
||||||
this.root.append(child);
|
this.root.append(child);
|
||||||
targetCanvas = new TinyCanvas({
|
targetCanvas = new TinyCanvas({
|
||||||
container: child.get<HTMLElement>()!,
|
container: child.get<HTMLElement>()!,
|
||||||
|
@ -118,6 +112,12 @@ class RangeColoring implements RangeColoringInterface {
|
||||||
|
|
||||||
child[0]['__canvas'] = targetCanvas;
|
child[0]['__canvas'] = targetCanvas;
|
||||||
}
|
}
|
||||||
|
child.css({
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
'pointer-events': 'none',
|
||||||
|
});
|
||||||
child[0]['__range'] = range.cloneRange();
|
child[0]['__range'] = range.cloneRange();
|
||||||
const parentWidth = this.root.width();
|
const parentWidth = this.root.width();
|
||||||
const parentHeight = this.root.height();
|
const parentHeight = this.root.height();
|
||||||
|
@ -140,6 +140,14 @@ class RangeColoring implements RangeColoringInterface {
|
||||||
if (!!result) {
|
if (!!result) {
|
||||||
if (Array.isArray(result)) subRanges = result;
|
if (Array.isArray(result)) subRanges = result;
|
||||||
else {
|
else {
|
||||||
|
if (result.x < 0) {
|
||||||
|
targetCanvas.resize(
|
||||||
|
parentWidth - result.x,
|
||||||
|
parentHeight,
|
||||||
|
);
|
||||||
|
child.css('left', `${result.x}px`);
|
||||||
|
result.x = 0;
|
||||||
|
}
|
||||||
targetCanvas.clearRect(result);
|
targetCanvas.clearRect(result);
|
||||||
targetCanvas.drawRect({ ...result.toJSON(), ...fill });
|
targetCanvas.drawRect({ ...result.toJSON(), ...fill });
|
||||||
return [range];
|
return [range];
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { EditorInterface } from '../types/engine';
|
||||||
import { PluginOptions, PluginInterface } from '../types/plugin';
|
import { PluginOptions, PluginInterface } from '../types/plugin';
|
||||||
|
|
||||||
abstract class PluginEntry<T extends PluginOptions = {}>
|
abstract class PluginEntry<T extends PluginOptions = {}>
|
||||||
implements PluginInterface
|
implements PluginInterface<T>
|
||||||
{
|
{
|
||||||
protected readonly editor: EditorInterface;
|
protected readonly editor: EditorInterface;
|
||||||
protected options: T;
|
options: T;
|
||||||
constructor(editor: EditorInterface, options: PluginOptions) {
|
constructor(editor: EditorInterface, options: PluginOptions) {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
this.options = (options || {}) as T;
|
this.options = (options || {}) as T;
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Position {
|
||||||
#root?: NodeInterface;
|
#root?: NodeInterface;
|
||||||
#onUpdate?: (rect: any) => void;
|
#onUpdate?: (rect: any) => void;
|
||||||
#updateTimeout?: NodeJS.Timeout;
|
#updateTimeout?: NodeJS.Timeout;
|
||||||
#observer?: MutationObserver;
|
#observer?: ResizeObserver;
|
||||||
|
|
||||||
constructor(editor: EditorInterface) {
|
constructor(editor: EditorInterface) {
|
||||||
this.#editor = editor;
|
this.#editor = editor;
|
||||||
|
@ -43,7 +43,7 @@ class Position {
|
||||||
this.#editor.scrollNode?.on('scroll', this.updateListener);
|
this.#editor.scrollNode?.on('scroll', this.updateListener);
|
||||||
}
|
}
|
||||||
let size = { width: target.width(), height: target.height() };
|
let size = { width: target.width(), height: target.height() };
|
||||||
this.#observer = new MutationObserver(() => {
|
this.#observer = new ResizeObserver(() => {
|
||||||
const width = target.width();
|
const width = target.width();
|
||||||
const height = target.height();
|
const height = target.height();
|
||||||
|
|
||||||
|
@ -54,13 +54,7 @@ class Position {
|
||||||
};
|
};
|
||||||
this.updateListener();
|
this.updateListener();
|
||||||
});
|
});
|
||||||
this.#observer.observe(target.get<HTMLElement>()!, {
|
this.#observer.observe(target.get<HTMLElement>()!);
|
||||||
attributes: true,
|
|
||||||
attributeFilter: ['style'],
|
|
||||||
attributeOldValue: true,
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
});
|
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.data-image-resizer {
|
.data-resizer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -7,37 +7,10 @@
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
outline: 2px solid #1890FF;
|
||||||
|
max-width: initial !important;
|
||||||
}
|
}
|
||||||
.data-image-resizer-holder {
|
.data-resizer img {
|
||||||
position: absolute;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border: 2px solid #fff;
|
|
||||||
background: #1890FF;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.data-image-resizer-holder-right-top {
|
|
||||||
top: -6px;
|
|
||||||
right: -6px;
|
|
||||||
cursor: nesw-resize;
|
|
||||||
}
|
|
||||||
.data-image-resizer-holder-right-bottom {
|
|
||||||
bottom: -6px;
|
|
||||||
right: -6px;
|
|
||||||
cursor: nwse-resize;
|
|
||||||
}
|
|
||||||
.data-image-resizer-holder-left-bottom {
|
|
||||||
bottom: -6px;
|
|
||||||
left: -6px;
|
|
||||||
cursor: nesw-resize;
|
|
||||||
}
|
|
||||||
.data-image-resizer-holder-left-top {
|
|
||||||
left: -6px;
|
|
||||||
top: -6px;
|
|
||||||
cursor: nwse-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-image-resizer-bg {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -46,13 +19,39 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.data-image-resizer-bg-active {
|
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
.data-resizer-holder {
|
||||||
|
position: absolute;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #1890FF;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.data-resizer-holder-right-top {
|
||||||
|
top: -6px;
|
||||||
|
right: -6px;
|
||||||
|
cursor: nesw-resize;
|
||||||
|
}
|
||||||
|
.data-resizer-holder-right-bottom {
|
||||||
|
bottom: -6px;
|
||||||
|
right: -6px;
|
||||||
|
cursor: nwse-resize;
|
||||||
|
}
|
||||||
|
.data-resizer-holder-left-bottom {
|
||||||
|
bottom: -6px;
|
||||||
|
left: -6px;
|
||||||
|
cursor: nesw-resize;
|
||||||
|
}
|
||||||
|
.data-resizer-holder-left-top {
|
||||||
|
left: -6px;
|
||||||
|
top: -6px;
|
||||||
|
cursor: nwse-resize;
|
||||||
|
}
|
||||||
|
|
||||||
.data-image-resizer-number {
|
.data-resizer-number {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
|
@ -68,31 +67,31 @@
|
||||||
transform: scale(0.8);
|
transform: scale(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-image-resizer-number-right-top {
|
.data-resizer-number-right-top {
|
||||||
top: 0px;
|
top: 0px;
|
||||||
right: -6px;
|
right: -6px;
|
||||||
transform: translateX(100%) scale(0.8);
|
transform: translateX(100%) scale(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-image-resizer-number-right-bottom {
|
.data-resizer-number-right-bottom {
|
||||||
right: -6px;
|
right: -6px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
transform: translateX(100%) scale(0.8);
|
transform: translateX(100%) scale(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-image-resizer-number-left-bottom {
|
.data-resizer-number-left-bottom {
|
||||||
left: -6px;
|
left: -6px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
transform: translateX(-100%) scale(0.8);
|
transform: translateX(-100%) scale(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-image-resizer-number-left-top {
|
.data-resizer-number-left-top {
|
||||||
left: -6px;
|
left: -6px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
transform: translateX(-100%) scale(0.8);
|
transform: translateX(-100%) scale(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-image-resizer-number-active {
|
.data-resizer-number-active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
|
@ -1,38 +1,22 @@
|
||||||
import { $, NodeInterface, EventListener, isMobile } from '@aomao/engine';
|
import type { NodeInterface, EventListener } from '../types';
|
||||||
|
import type {
|
||||||
|
ResizerInterface,
|
||||||
|
ResizerOptions,
|
||||||
|
Point,
|
||||||
|
ResizerPosition,
|
||||||
|
Size,
|
||||||
|
} from '../types';
|
||||||
|
import { $ } from '../node';
|
||||||
|
import { isMobile } from '../utils';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
export type Options = {
|
class Resizer implements ResizerInterface {
|
||||||
src: string;
|
private options: ResizerOptions;
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
maxWidth: number;
|
|
||||||
rate: number;
|
|
||||||
onChange?: (size: Size) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Position =
|
|
||||||
| 'right-top'
|
|
||||||
| 'left-top'
|
|
||||||
| 'right-bottom'
|
|
||||||
| 'left-bottom';
|
|
||||||
|
|
||||||
export type Point = {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Size = {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Resizer {
|
|
||||||
private options: Options;
|
|
||||||
private root: NodeInterface;
|
private root: NodeInterface;
|
||||||
private image: NodeInterface;
|
private image?: NodeInterface;
|
||||||
private resizerNumber: NodeInterface;
|
private resizerNumber: NodeInterface;
|
||||||
private point: Point = { x: 0, y: 0 };
|
private point: Point = { x: 0, y: 0 };
|
||||||
private position?: Position;
|
private position?: ResizerPosition;
|
||||||
private size: Size;
|
private size: Size;
|
||||||
maxWidth: number;
|
maxWidth: number;
|
||||||
/**
|
/**
|
||||||
|
@ -40,11 +24,12 @@ class Resizer {
|
||||||
*/
|
*/
|
||||||
private resizing: boolean = false;
|
private resizing: boolean = false;
|
||||||
|
|
||||||
constructor(options: Options) {
|
constructor(options: ResizerOptions) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.root = $(this.renderTemplate(options.src));
|
this.root = $(this.renderTemplate(options.imgUrl));
|
||||||
this.image = this.root.find('img');
|
if (options.imgUrl) this.image = this.root.find('img');
|
||||||
this.resizerNumber = this.root.find('.data-image-resizer-number');
|
this.image?.hide();
|
||||||
|
this.resizerNumber = this.root.find('.data-resizer-number');
|
||||||
const { width, height } = this.options;
|
const { width, height } = this.options;
|
||||||
this.size = {
|
this.size = {
|
||||||
width,
|
width,
|
||||||
|
@ -53,19 +38,19 @@ class Resizer {
|
||||||
this.maxWidth = this.options.maxWidth;
|
this.maxWidth = this.options.maxWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTemplate(src: string) {
|
renderTemplate(imgUrl?: string) {
|
||||||
return `
|
return `
|
||||||
<div class="data-image-resizer">
|
<div class="data-resizer">
|
||||||
<img class="data-image-resizer-bg data-image-resizer-bg-active" src="${src}" />
|
${imgUrl ? `<img src="${imgUrl}">` : ''}
|
||||||
<div class="data-image-resizer-holder data-image-resizer-holder-right-top"></div>
|
<div class="data-resizer-holder data-resizer-holder-right-top"></div>
|
||||||
<div class="data-image-resizer-holder data-image-resizer-holder-right-bottom"></div>
|
<div class="data-resizer-holder data-resizer-holder-right-bottom"></div>
|
||||||
<div class="data-image-resizer-holder data-image-resizer-holder-left-bottom"></div>
|
<div class="data-resizer-holder data-resizer-holder-left-bottom"></div>
|
||||||
<div class="data-image-resizer-holder data-image-resizer-holder-left-top"></div>
|
<div class="data-resizer-holder data-resizer-holder-left-top"></div>
|
||||||
<span class="data-image-resizer-number"></span>
|
<span class="data-resizer-number"></span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseDown(event: MouseEvent | TouchEvent, position: Position) {
|
onMouseDown(event: MouseEvent | TouchEvent, position: ResizerPosition) {
|
||||||
if (this.resizing) return;
|
if (this.resizing) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@ -97,11 +82,10 @@ class Resizer {
|
||||||
};
|
};
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.resizing = true;
|
this.resizing = true;
|
||||||
this.resizerNumber.addClass(
|
this.root.addClass('data-resizing');
|
||||||
`data-image-resizer-number-${this.position}`,
|
this.resizerNumber.addClass(`data-resizer-number-${this.position}`);
|
||||||
);
|
this.resizerNumber.addClass('data-resizer-number-active');
|
||||||
this.resizerNumber.addClass('data-image-resizer-number-active');
|
this.image?.show();
|
||||||
this.image.show();
|
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
isMobile ? 'touchmove' : 'mousemove',
|
isMobile ? 'touchmove' : 'mousemove',
|
||||||
this.onMouseMove,
|
this.onMouseMove,
|
||||||
|
@ -140,13 +124,11 @@ class Resizer {
|
||||||
width: clientWidth,
|
width: clientWidth,
|
||||||
height: clientHeight,
|
height: clientHeight,
|
||||||
};
|
};
|
||||||
this.resizerNumber.removeClass(
|
this.resizerNumber.removeClass(`data-resizer-number-${this.position}`);
|
||||||
`data-image-resizer-number-${this.position}`,
|
this.resizerNumber.removeClass('data-resizer-number-active');
|
||||||
);
|
|
||||||
this.resizerNumber.removeClass('data-image-resizer-number-active');
|
|
||||||
this.position = undefined;
|
this.position = undefined;
|
||||||
this.resizing = false;
|
this.resizing = false;
|
||||||
|
this.root.removeClass('data-resizing');
|
||||||
document.removeEventListener(
|
document.removeEventListener(
|
||||||
isMobile ? 'touchmove' : 'mousemove',
|
isMobile ? 'touchmove' : 'mousemove',
|
||||||
this.onMouseMove,
|
this.onMouseMove,
|
||||||
|
@ -157,7 +139,7 @@ class Resizer {
|
||||||
);
|
);
|
||||||
const { onChange } = this.options;
|
const { onChange } = this.options;
|
||||||
if (onChange) onChange(this.size);
|
if (onChange) onChange(this.size);
|
||||||
this.image.hide();
|
this.image?.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
updateSize(width: number, height: number) {
|
updateSize(width: number, height: number) {
|
||||||
|
@ -166,6 +148,10 @@ class Resizer {
|
||||||
} else {
|
} else {
|
||||||
width = this.size.width + width;
|
width = this.size.width + width;
|
||||||
}
|
}
|
||||||
|
this.setSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSize(width: number, height: number) {
|
||||||
if (width < 24) {
|
if (width < 24) {
|
||||||
width = 24;
|
width = 24;
|
||||||
}
|
}
|
||||||
|
@ -181,10 +167,6 @@ class Resizer {
|
||||||
}
|
}
|
||||||
width = Math.round(width);
|
width = Math.round(width);
|
||||||
height = Math.round(height);
|
height = Math.round(height);
|
||||||
this.setSize(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSize(width: number, height: number) {
|
|
||||||
this.root.css({
|
this.root.css({
|
||||||
width: width + 'px',
|
width: width + 'px',
|
||||||
height: height + 'px',
|
height: height + 'px',
|
||||||
|
@ -193,37 +175,33 @@ class Resizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
on(eventType: string, listener: EventListener) {
|
on(eventType: string, listener: EventListener) {
|
||||||
this.image.on(eventType, listener);
|
this.image?.on(eventType, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
off(eventType: string, listener: EventListener) {
|
off(eventType: string, listener: EventListener) {
|
||||||
this.image.off(eventType, listener);
|
this.image?.off(eventType, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { width, height } = this.options;
|
const { width, height } = this.options;
|
||||||
this.root.css({
|
this.setSize(width, height);
|
||||||
width: `${width}px`,
|
|
||||||
height: `${height}px`,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.root
|
this.root
|
||||||
.find('.data-image-resizer-holder-right-top')
|
.find('.data-resizer-holder-right-top')
|
||||||
.on(isMobile ? 'touchstart' : 'mousedown', (event) => {
|
.on(isMobile ? 'touchstart' : 'mousedown', (event) => {
|
||||||
return this.onMouseDown(event, 'right-top');
|
return this.onMouseDown(event, 'right-top');
|
||||||
});
|
});
|
||||||
this.root
|
this.root
|
||||||
.find('.data-image-resizer-holder-right-bottom')
|
.find('.data-resizer-holder-right-bottom')
|
||||||
.on(isMobile ? 'touchstart' : 'mousedown', (event) => {
|
.on(isMobile ? 'touchstart' : 'mousedown', (event) => {
|
||||||
return this.onMouseDown(event, 'right-bottom');
|
return this.onMouseDown(event, 'right-bottom');
|
||||||
});
|
});
|
||||||
this.root
|
this.root
|
||||||
.find('.data-image-resizer-holder-left-bottom')
|
.find('.data-resizer-holder-left-bottom')
|
||||||
.on(isMobile ? 'touchstart' : 'mousedown', (event) => {
|
.on(isMobile ? 'touchstart' : 'mousedown', (event) => {
|
||||||
return this.onMouseDown(event, 'left-bottom');
|
return this.onMouseDown(event, 'left-bottom');
|
||||||
});
|
});
|
||||||
this.root
|
this.root
|
||||||
.find('.data-image-resizer-holder-left-top')
|
.find('.data-resizer-holder-left-top')
|
||||||
.on(isMobile ? 'touchstart' : 'mousedown', (event) => {
|
.on(isMobile ? 'touchstart' : 'mousedown', (event) => {
|
||||||
return this.onMouseDown(event, 'left-top');
|
return this.onMouseDown(event, 'left-top');
|
||||||
});
|
});
|
|
@ -40,7 +40,6 @@
|
||||||
background: #888;
|
background: #888;
|
||||||
}
|
}
|
||||||
.data-scrollable .data-scrollbar.data-scrollbar-x {
|
.data-scrollable .data-scrollbar.data-scrollbar-x {
|
||||||
width: 100%;
|
|
||||||
height: 8px;
|
height: 8px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { EventEmitter2 } from 'eventemitter2';
|
import { EventEmitter2 } from 'eventemitter2';
|
||||||
|
import domAlign from 'dom-align';
|
||||||
import { DATA_ELEMENT, UI } from '../constants';
|
import { DATA_ELEMENT, UI } from '../constants';
|
||||||
import { NodeInterface } from '../types';
|
import { NodeInterface } from '../types';
|
||||||
import { $ } from '../node';
|
import { $ } from '../node';
|
||||||
|
@ -11,6 +12,12 @@ export type ScrollbarDragging = {
|
||||||
position: number;
|
position: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ScrollbarCustomeOptions = {
|
||||||
|
onScrollX?: (x: number) => number;
|
||||||
|
getOffsetWidth?: (width: number) => number;
|
||||||
|
getScrollLeft?: (left: number) => number;
|
||||||
|
};
|
||||||
|
|
||||||
class Scrollbar extends EventEmitter2 {
|
class Scrollbar extends EventEmitter2 {
|
||||||
private container: NodeInterface;
|
private container: NodeInterface;
|
||||||
private x: boolean;
|
private x: boolean;
|
||||||
|
@ -36,6 +43,7 @@ class Scrollbar extends EventEmitter2 {
|
||||||
#content?: NodeInterface;
|
#content?: NodeInterface;
|
||||||
shadowTimer?: NodeJS.Timeout;
|
shadowTimer?: NodeJS.Timeout;
|
||||||
#enableScroll: boolean = true;
|
#enableScroll: boolean = true;
|
||||||
|
#scroll?: ScrollbarCustomeOptions;
|
||||||
/**
|
/**
|
||||||
* @param {nativeNode} container 需要添加滚动条的元素
|
* @param {nativeNode} container 需要添加滚动条的元素
|
||||||
* @param {boolean} x 横向滚动条
|
* @param {boolean} x 横向滚动条
|
||||||
|
@ -47,12 +55,14 @@ class Scrollbar extends EventEmitter2 {
|
||||||
x: boolean = true,
|
x: boolean = true,
|
||||||
y: boolean = false,
|
y: boolean = false,
|
||||||
shadow: boolean = true,
|
shadow: boolean = true,
|
||||||
|
scroll?: ScrollbarCustomeOptions,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.container = isNode(container) ? $(container) : container;
|
this.container = isNode(container) ? $(container) : container;
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.shadow = shadow;
|
this.shadow = shadow;
|
||||||
|
this.#scroll = scroll;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +82,7 @@ class Scrollbar extends EventEmitter2 {
|
||||||
const children = this.container.children();
|
const children = this.container.children();
|
||||||
let hasScrollbar = false;
|
let hasScrollbar = false;
|
||||||
children.each((child) => {
|
children.each((child) => {
|
||||||
if ($(child).hasClass('data-scrollbar')) {
|
if (!hasScrollbar && $(child).hasClass('data-scrollbar')) {
|
||||||
hasScrollbar = true;
|
hasScrollbar = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -81,7 +91,7 @@ class Scrollbar extends EventEmitter2 {
|
||||||
this.container.addClass('data-scrollable');
|
this.container.addClass('data-scrollable');
|
||||||
if (this.x) {
|
if (this.x) {
|
||||||
this.scrollBarX = $(
|
this.scrollBarX = $(
|
||||||
`<div ${DATA_ELEMENT}="${UI}" class="data-scrollbar data-scrollbar-x "><div class="data-scrollbar-trigger"></div></div>`,
|
`<div ${DATA_ELEMENT}="${UI}" class="data-scrollbar data-scrollbar-x"><div class="data-scrollbar-trigger"></div></div>`,
|
||||||
);
|
);
|
||||||
this.slideX = this.scrollBarX.find('.data-scrollbar-trigger');
|
this.slideX = this.scrollBarX.find('.data-scrollbar-trigger');
|
||||||
this.container.append(this.scrollBarX);
|
this.container.append(this.scrollBarX);
|
||||||
|
@ -89,7 +99,7 @@ class Scrollbar extends EventEmitter2 {
|
||||||
}
|
}
|
||||||
if (this.y) {
|
if (this.y) {
|
||||||
this.scrollBarY = $(
|
this.scrollBarY = $(
|
||||||
`<div ${DATA_ELEMENT}="${UI}" class="data-scrollbar data-scrollbar-y "><div class="data-scrollbar-trigger"></div></div>`,
|
`<div ${DATA_ELEMENT}="${UI}" class="data-scrollbar data-scrollbar-y"><div class="data-scrollbar-trigger"></div></div>`,
|
||||||
);
|
);
|
||||||
this.slideY = this.scrollBarY.find('.data-scrollbar-trigger');
|
this.slideY = this.scrollBarY.find('.data-scrollbar-trigger');
|
||||||
this.container.append(this.scrollBarY);
|
this.container.append(this.scrollBarY);
|
||||||
|
@ -110,12 +120,14 @@ class Scrollbar extends EventEmitter2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh = () => {
|
||||||
const element = this.container.get<HTMLElement>();
|
const element = this.container.get<HTMLElement>();
|
||||||
if (element) {
|
if (element) {
|
||||||
const { offsetWidth, offsetHeight, scrollLeft, scrollTop } =
|
const offsetWidth = this.#scroll?.getOffsetWidth
|
||||||
element;
|
? this.#scroll.getOffsetWidth(element.offsetWidth)
|
||||||
|
: element.offsetWidth;
|
||||||
|
|
||||||
|
const { offsetHeight, scrollTop } = element;
|
||||||
const contentElement = this.#content?.get<HTMLElement>();
|
const contentElement = this.#content?.get<HTMLElement>();
|
||||||
const sPLeft = removeUnit(this.container.css('padding-left'));
|
const sPLeft = removeUnit(this.container.css('padding-left'));
|
||||||
const sPRight = removeUnit(this.container.css('padding-right'));
|
const sPRight = removeUnit(this.container.css('padding-right'));
|
||||||
|
@ -170,14 +182,30 @@ class Scrollbar extends EventEmitter2 {
|
||||||
if (
|
if (
|
||||||
this.x &&
|
this.x &&
|
||||||
contentElement &&
|
contentElement &&
|
||||||
element.scrollWidth - sPLeft - sPRight !==
|
element.scrollWidth - sPLeft - sPRight >
|
||||||
contentElement.offsetWidth
|
contentElement.offsetWidth
|
||||||
) {
|
) {
|
||||||
element.scrollLeft -=
|
let left =
|
||||||
element.scrollWidth -
|
element.scrollWidth -
|
||||||
sPLeft -
|
sPLeft -
|
||||||
sPRight -
|
sPRight -
|
||||||
contentElement.offsetWidth;
|
contentElement.offsetWidth;
|
||||||
|
if (this.#scroll) {
|
||||||
|
const { onScrollX, getScrollLeft } = this.#scroll;
|
||||||
|
|
||||||
|
left = getScrollLeft
|
||||||
|
? getScrollLeft(-0) + element.scrollLeft - left
|
||||||
|
: element.scrollLeft - left;
|
||||||
|
|
||||||
|
if (onScrollX) {
|
||||||
|
const result = onScrollX(left);
|
||||||
|
if (result > 0) element.scrollLeft = result;
|
||||||
|
else element.scrollLeft = 0;
|
||||||
|
}
|
||||||
|
this.scroll({ left });
|
||||||
|
} else {
|
||||||
|
element.scrollLeft -= left;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 实际内容高度小于容器滚动高度(有内容删除了)
|
// 实际内容高度小于容器滚动高度(有内容删除了)
|
||||||
|
@ -194,10 +222,13 @@ class Scrollbar extends EventEmitter2 {
|
||||||
contentElement.offsetHeight;
|
contentElement.offsetHeight;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.reRenderX(scrollLeft);
|
const left = this.#scroll?.getScrollLeft
|
||||||
|
? this.#scroll.getScrollLeft(element.scrollLeft)
|
||||||
|
: element.scrollLeft;
|
||||||
|
this.reRenderX(left);
|
||||||
this.reRenderY(scrollTop);
|
this.reRenderY(scrollTop);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启用鼠标在内容节点上滚动或在移动设备使用手指滑动
|
* 启用鼠标在内容节点上滚动或在移动设备使用手指滑动
|
||||||
|
@ -212,13 +243,26 @@ class Scrollbar extends EventEmitter2 {
|
||||||
this.#enableScroll = false;
|
this.#enableScroll = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
scroll = (event: Event) => {
|
scroll = (event: Event | { top?: number; left?: number }) => {
|
||||||
const { target } = event;
|
let top = 0;
|
||||||
if (!target) return;
|
let left = 0;
|
||||||
|
if (!this.#scroll && event instanceof Event) {
|
||||||
|
const { scrollTop, scrollLeft } = event.target as HTMLElement;
|
||||||
|
top = scrollTop;
|
||||||
|
left = scrollLeft;
|
||||||
|
} else if (!(event instanceof Event)) {
|
||||||
|
if (event.top === undefined) {
|
||||||
|
event.top = this.container.get<HTMLElement>()?.scrollTop || 0;
|
||||||
|
}
|
||||||
|
if (event.left === undefined) {
|
||||||
|
event.left = this.container.get<HTMLElement>()?.scrollLeft || 0;
|
||||||
|
}
|
||||||
|
top = event.top;
|
||||||
|
left = event.left;
|
||||||
|
} else return;
|
||||||
|
|
||||||
const { scrollTop, scrollLeft } = target as HTMLElement;
|
this.reRenderX(left);
|
||||||
this.reRenderX(scrollLeft);
|
this.reRenderY(top);
|
||||||
this.reRenderY(scrollTop);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
wheelXScroll = (event: any) => {
|
wheelXScroll = (event: any) => {
|
||||||
|
@ -227,12 +271,29 @@ class Scrollbar extends EventEmitter2 {
|
||||||
const dir = wheelValue > 0 ? 'up' : 'down';
|
const dir = wheelValue > 0 ? 'up' : 'down';
|
||||||
const containerElement = this.container.get<HTMLElement>();
|
const containerElement = this.container.get<HTMLElement>();
|
||||||
if (!containerElement) return;
|
if (!containerElement) return;
|
||||||
let left = containerElement.scrollLeft + (dir === 'up' ? -20 : 20);
|
const containerWidth = this.#scroll?.getOffsetWidth
|
||||||
|
? this.#scroll.getOffsetWidth(containerElement.offsetWidth)
|
||||||
|
: containerElement.offsetWidth;
|
||||||
|
const step = Math.max(containerWidth / 8, 20);
|
||||||
|
let left =
|
||||||
|
(this.#scroll?.getScrollLeft
|
||||||
|
? this.#scroll.getScrollLeft(containerElement.scrollLeft)
|
||||||
|
: containerElement.scrollLeft) + (dir === 'up' ? -step : step);
|
||||||
left =
|
left =
|
||||||
dir === 'up'
|
dir === 'up'
|
||||||
? Math.max(0, left)
|
? Math.max(0, left)
|
||||||
: Math.min(left, this.sWidth - this.oWidth);
|
: Math.min(left, this.sWidth - this.oWidth);
|
||||||
containerElement.scrollLeft = left;
|
if (this.#scroll) {
|
||||||
|
const { onScrollX } = this.#scroll;
|
||||||
|
if (onScrollX) {
|
||||||
|
const result = onScrollX(left);
|
||||||
|
if (result > 0) containerElement.scrollLeft = result;
|
||||||
|
else containerElement.scrollLeft = 0;
|
||||||
|
}
|
||||||
|
this.scroll({ left });
|
||||||
|
} else {
|
||||||
|
containerElement.scrollLeft = left;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
wheelYScroll = (event: any) => {
|
wheelYScroll = (event: any) => {
|
||||||
|
@ -241,7 +302,9 @@ class Scrollbar extends EventEmitter2 {
|
||||||
const dir = wheelValue > 0 ? 'up' : 'down';
|
const dir = wheelValue > 0 ? 'up' : 'down';
|
||||||
const containerElement = this.container.get<HTMLElement>();
|
const containerElement = this.container.get<HTMLElement>();
|
||||||
if (!containerElement) return;
|
if (!containerElement) return;
|
||||||
let top = containerElement.scrollTop + (dir === 'up' ? -20 : 20);
|
const containerHeight = containerElement.offsetHeight;
|
||||||
|
const step = Math.max(containerHeight / 8, 20);
|
||||||
|
let top = containerElement.scrollTop + (dir === 'up' ? -step : step);
|
||||||
top =
|
top =
|
||||||
dir === 'up'
|
dir === 'up'
|
||||||
? Math.max(0, top)
|
? Math.max(0, top)
|
||||||
|
@ -325,6 +388,7 @@ class Scrollbar extends EventEmitter2 {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
});
|
});
|
||||||
|
window.addEventListener('resize', this.refresh);
|
||||||
// 绑定滚动条事件
|
// 绑定滚动条事件
|
||||||
this.bindXScrollEvent();
|
this.bindXScrollEvent();
|
||||||
this.bindYScrollEvent();
|
this.bindYScrollEvent();
|
||||||
|
@ -361,8 +425,19 @@ class Scrollbar extends EventEmitter2 {
|
||||||
this.slideX?.css('left', left + 'px');
|
this.slideX?.css('left', left + 'px');
|
||||||
let min = left / (this.oWidth - this.xWidth);
|
let min = left / (this.oWidth - this.xWidth);
|
||||||
min = Math.min(1, min);
|
min = Math.min(1, min);
|
||||||
this.container.get<HTMLElement>()!.scrollLeft =
|
const containerElement = this.container.get<HTMLElement>()!;
|
||||||
(this.sWidth - this.oWidth) * min;
|
const x = (this.sWidth - this.oWidth) * min;
|
||||||
|
if (this.#scroll) {
|
||||||
|
const { onScrollX } = this.#scroll;
|
||||||
|
if (onScrollX) {
|
||||||
|
const result = onScrollX(x);
|
||||||
|
if (result > 0) containerElement.scrollLeft = result;
|
||||||
|
else containerElement.scrollLeft = 0;
|
||||||
|
}
|
||||||
|
this.scroll({ left: x });
|
||||||
|
} else {
|
||||||
|
containerElement.scrollLeft = x;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -463,7 +538,13 @@ class Scrollbar extends EventEmitter2 {
|
||||||
|
|
||||||
reRenderShadow = (width: number) => {
|
reRenderShadow = (width: number) => {
|
||||||
if (this.shadow) {
|
if (this.shadow) {
|
||||||
this.shadowLeft?.css('left', width + 'px');
|
const element = this.container.get<HTMLElement>();
|
||||||
|
if (element) {
|
||||||
|
this.shadowLeft?.css(
|
||||||
|
'left',
|
||||||
|
(this.#scroll ? element.scrollLeft : width) + 'px',
|
||||||
|
);
|
||||||
|
}
|
||||||
this.shadowRight?.css('left', width + this.oWidth - 4 + 'px');
|
this.shadowRight?.css('left', width + this.oWidth - 4 + 'px');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -476,7 +557,12 @@ class Scrollbar extends EventEmitter2 {
|
||||||
min = Math.min(1, min);
|
min = Math.min(1, min);
|
||||||
this.slideX?.css('left', (this.oWidth - this.xWidth) * min + 'px');
|
this.slideX?.css('left', (this.oWidth - this.xWidth) * min + 'px');
|
||||||
this.reRenderShadow(left);
|
this.reRenderShadow(left);
|
||||||
this.emit('change');
|
if (left === removeUnit(this.scrollBarX?.css('left') || '0'))
|
||||||
|
return;
|
||||||
|
this.emit('change', {
|
||||||
|
x: left,
|
||||||
|
y: removeUnit(this.scrollBarY?.css('top') || '0'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -487,7 +573,11 @@ class Scrollbar extends EventEmitter2 {
|
||||||
let min = value <= 0 ? 0 : top / value;
|
let min = value <= 0 ? 0 : top / value;
|
||||||
min = Math.min(1, min);
|
min = Math.min(1, min);
|
||||||
this.slideY?.css('top', (this.oHeight - this.yHeight) * min + 'px');
|
this.slideY?.css('top', (this.oHeight - this.yHeight) * min + 'px');
|
||||||
this.emit('change');
|
if (top === removeUnit(this.scrollBarX?.css('top') || '0')) return;
|
||||||
|
this.emit('change', {
|
||||||
|
x: removeUnit(this.scrollBarX?.css('left') || '0'),
|
||||||
|
y: top,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -526,6 +616,7 @@ class Scrollbar extends EventEmitter2 {
|
||||||
this.shadowRight?.remove();
|
this.shadowRight?.remove();
|
||||||
}
|
}
|
||||||
this.#observer?.disconnect();
|
this.#observer?.disconnect();
|
||||||
|
window.removeEventListener('resize', this.refresh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,9 @@
|
||||||
import { DATA_ELEMENT } from '../../constants/root';
|
import { DATA_ELEMENT } from '../../constants/root';
|
||||||
import { NodeInterface } from '../../types/node';
|
import { NodeInterface } from '../../types/node';
|
||||||
|
import { Placement } from '../../types/position';
|
||||||
import { $ } from '../../node';
|
import { $ } from '../../node';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
type Placement =
|
|
||||||
| 'top'
|
|
||||||
| 'topLeft'
|
|
||||||
| 'topRight'
|
|
||||||
| 'bottom'
|
|
||||||
| 'bottomLeft'
|
|
||||||
| 'bottomRight'
|
|
||||||
| 'left'
|
|
||||||
| 'right';
|
|
||||||
|
|
||||||
const template = (options: { placement: Placement }) => {
|
const template = (options: { placement: Placement }) => {
|
||||||
return `
|
return `
|
||||||
<div ${DATA_ELEMENT}="tooltip" class="data-tooltip data-tooltip-placement-${options.placement} data-tooltip-hidden" style="transform-origin: 50% 45px 0px;">
|
<div ${DATA_ELEMENT}="tooltip" class="data-tooltip data-tooltip-placement-${options.placement} data-tooltip-hidden" style="transform-origin: 50% 45px 0px;">
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
ToolbarItemOptions,
|
ToolbarItemOptions,
|
||||||
} from './toolbar';
|
} from './toolbar';
|
||||||
import { CardActiveTrigger, CardType } from '../card/enum';
|
import { CardActiveTrigger, CardType } from '../card/enum';
|
||||||
|
import { Placement } from './position';
|
||||||
|
|
||||||
export type CardOptions = {
|
export type CardOptions = {
|
||||||
editor: EditorInterface;
|
editor: EditorInterface;
|
||||||
|
@ -53,6 +54,7 @@ export interface CardToolbarInterface {
|
||||||
* @param offset 偏移量 [tx,ty,bx,by]
|
* @param offset 偏移量 [tx,ty,bx,by]
|
||||||
*/
|
*/
|
||||||
setOffset(offset: Array<number>): void;
|
setOffset(offset: Array<number>): void;
|
||||||
|
setDefaultAlign(align: Placement): void;
|
||||||
/**
|
/**
|
||||||
* 销毁
|
* 销毁
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,3 +21,5 @@ export * from './block';
|
||||||
export * from './request';
|
export * from './request';
|
||||||
export * from './tiny-canvas';
|
export * from './tiny-canvas';
|
||||||
export * from './parser';
|
export * from './parser';
|
||||||
|
export * from './resizer';
|
||||||
|
export * from './position';
|
||||||
|
|
|
@ -22,8 +22,12 @@ export interface PluginEntry {
|
||||||
readonly pluginName: string;
|
readonly pluginName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginInterface {
|
export interface PluginInterface<T extends PluginOptions = {}> {
|
||||||
readonly kind: string;
|
readonly kind: string;
|
||||||
|
/**
|
||||||
|
* 可选项
|
||||||
|
**/
|
||||||
|
options: T;
|
||||||
/**
|
/**
|
||||||
* 是否禁用,默认不禁用。在默认不指定的情况下,编辑器为 readonly 的时候全部禁用
|
* 是否禁用,默认不禁用。在默认不指定的情况下,编辑器为 readonly 的时候全部禁用
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
export type Placement =
|
||||||
|
| 'top'
|
||||||
|
| 'topLeft'
|
||||||
|
| 'topRight'
|
||||||
|
| 'bottom'
|
||||||
|
| 'bottomLeft'
|
||||||
|
| 'bottomRight'
|
||||||
|
| 'left'
|
||||||
|
| 'leftTop'
|
||||||
|
| 'leftBottom'
|
||||||
|
| 'right'
|
||||||
|
| 'rightTop'
|
||||||
|
| 'rightBottom';
|
|
@ -0,0 +1,33 @@
|
||||||
|
export interface ResizerInterface {
|
||||||
|
on(eventType: string, listener: EventListener): void;
|
||||||
|
off(eventType: string, listener: EventListener): void;
|
||||||
|
setSize(width: number, height: number): void;
|
||||||
|
maxWidth: number;
|
||||||
|
render(): void;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ResizerOptions = {
|
||||||
|
imgUrl?: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
maxWidth: number;
|
||||||
|
rate: number;
|
||||||
|
onChange?: (size: Size) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResizerPosition =
|
||||||
|
| 'right-top'
|
||||||
|
| 'left-top'
|
||||||
|
| 'right-bottom'
|
||||||
|
| 'left-bottom';
|
||||||
|
|
||||||
|
export type Point = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Size = {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import classnames from 'classnames-es-ts';
|
import classnames from 'classnames-es-ts';
|
||||||
import { EngineInterface } from '@aomao/engine';
|
import { EngineInterface, Placement } from '@aomao/engine';
|
||||||
import Popover from 'antd/es/popover';
|
import Popover from 'antd/es/popover';
|
||||||
import 'antd/es/popover/style';
|
import 'antd/es/popover/style';
|
||||||
|
|
||||||
|
@ -17,19 +17,7 @@ export type CollapseItemProps = {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onDisabled?: () => boolean;
|
onDisabled?: () => boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
placement?:
|
placement?: Placement;
|
||||||
| 'right'
|
|
||||||
| 'top'
|
|
||||||
| 'left'
|
|
||||||
| 'bottom'
|
|
||||||
| 'topLeft'
|
|
||||||
| 'topRight'
|
|
||||||
| 'bottomLeft'
|
|
||||||
| 'bottomRight'
|
|
||||||
| 'leftTop'
|
|
||||||
| 'leftBottom'
|
|
||||||
| 'rightTop'
|
|
||||||
| 'rightBottom';
|
|
||||||
onClick?: (event: React.MouseEvent, name: string) => void | boolean;
|
onClick?: (event: React.MouseEvent, name: string) => void | boolean;
|
||||||
onMouseDown?: (event: React.MouseEvent) => void;
|
onMouseDown?: (event: React.MouseEvent) => void;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import { PswpInterface } from '@/types';
|
import type { PswpInterface } from '@/types';
|
||||||
|
import type { EditorInterface, NodeInterface } from '@aomao/engine';
|
||||||
import {
|
import {
|
||||||
$,
|
$,
|
||||||
EditorInterface,
|
|
||||||
isEngine,
|
isEngine,
|
||||||
escape,
|
escape,
|
||||||
NodeInterface,
|
|
||||||
sanitizeUrl,
|
sanitizeUrl,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
isMobile,
|
isMobile,
|
||||||
|
Resizer,
|
||||||
CardType,
|
CardType,
|
||||||
} from '@aomao/engine';
|
} from '@aomao/engine';
|
||||||
import Pswp from '../pswp';
|
import Pswp from '../pswp';
|
||||||
import Resizer from '../resizer';
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
export type Status = 'uploading' | 'done' | 'error';
|
export type Status = 'uploading' | 'done' | 'error';
|
||||||
|
@ -295,12 +294,12 @@ class Image {
|
||||||
this.meta.css({
|
this.meta.css({
|
||||||
'background-color': '',
|
'background-color': '',
|
||||||
width: '',
|
width: '',
|
||||||
height: '',
|
//height: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
this.image.css({
|
this.image.css({
|
||||||
width: '',
|
width: '',
|
||||||
height: '',
|
//height: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const img = this.image.get<HTMLImageElement>();
|
const img = this.image.get<HTMLImageElement>();
|
||||||
|
@ -334,7 +333,7 @@ class Image {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.image.css('width', `${width}px`);
|
this.image.css('width', `${width}px`);
|
||||||
this.image.css('height', `${height}px`);
|
//this.image.css('height', `${height}px`);
|
||||||
}
|
}
|
||||||
|
|
||||||
changeSize(width: number, height: number) {
|
changeSize(width: number, height: number) {
|
||||||
|
@ -359,7 +358,7 @@ class Image {
|
||||||
this.size.height = height;
|
this.size.height = height;
|
||||||
this.image.css({
|
this.image.css({
|
||||||
width: `${width}px`,
|
width: `${width}px`,
|
||||||
height: `${height}px`,
|
//height: `${height}px`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { onChange } = this.options;
|
const { onChange } = this.options;
|
||||||
|
@ -450,7 +449,7 @@ class Image {
|
||||||
if (isMobile || !isEngine(this.editor) || this.editor.readonly) return;
|
if (isMobile || !isEngine(this.editor) || this.editor.readonly) return;
|
||||||
// 拖动调整图片大小
|
// 拖动调整图片大小
|
||||||
const resizer = new Resizer({
|
const resizer = new Resizer({
|
||||||
src: this.getSrc(),
|
imgUrl: this.getSrc(),
|
||||||
width: clientWidth,
|
width: clientWidth,
|
||||||
height: clientHeight,
|
height: clientHeight,
|
||||||
rate: this.rate,
|
rate: this.rate,
|
||||||
|
@ -542,7 +541,7 @@ class Image {
|
||||||
if (this.src) {
|
if (this.src) {
|
||||||
this.image.css({
|
this.image.css({
|
||||||
width: width + 'px',
|
width: width + 'px',
|
||||||
height: height + 'px',
|
//height: height + 'px',
|
||||||
});
|
});
|
||||||
const { onChange } = this.options;
|
const { onChange } = this.options;
|
||||||
if (width > 0 && height > 0) {
|
if (width > 0 && height > 0) {
|
||||||
|
|
|
@ -228,6 +228,23 @@ class ImageComponent extends Card<ImageValue> {
|
||||||
else this.image?.blur();
|
else this.image?.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSelectByOther(
|
||||||
|
selected: boolean,
|
||||||
|
value?: {
|
||||||
|
color: string;
|
||||||
|
rgb: string;
|
||||||
|
},
|
||||||
|
): NodeInterface | void {
|
||||||
|
this.image?.root?.css(
|
||||||
|
'outline',
|
||||||
|
selected ? '2px solid ' + value!.color : '',
|
||||||
|
);
|
||||||
|
const className = 'card-selected-other';
|
||||||
|
if (selected) this.root.addClass(className);
|
||||||
|
else this.root.removeClass(className);
|
||||||
|
return this.image?.root;
|
||||||
|
}
|
||||||
|
|
||||||
render(loadingBg?: string): string | void | NodeInterface {
|
render(loadingBg?: string): string | void | NodeInterface {
|
||||||
const value = this.getValue();
|
const value = this.getValue();
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
|
@ -253,20 +270,6 @@ class ImageComponent extends Card<ImageValue> {
|
||||||
},
|
},
|
||||||
onChange: (size) => {
|
onChange: (size) => {
|
||||||
if (size) this.setSize(size);
|
if (size) this.setSize(size);
|
||||||
if (this.type === CardType.BLOCK && this.image) {
|
|
||||||
const maxWidth = this.image.getMaxWidth();
|
|
||||||
const offset = (maxWidth - this.image.root.width()) / 2;
|
|
||||||
if (value.status === 'done') {
|
|
||||||
this.toolbarModel?.setOffset([
|
|
||||||
-offset - 12,
|
|
||||||
0,
|
|
||||||
-offset - 12,
|
|
||||||
0,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (this.activated)
|
|
||||||
this.toolbarModel?.showCardToolbar();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
this.isLocalError = true;
|
this.isLocalError = true;
|
||||||
|
@ -290,12 +293,8 @@ class ImageComponent extends Card<ImageValue> {
|
||||||
}
|
}
|
||||||
|
|
||||||
didRender() {
|
didRender() {
|
||||||
if (
|
super.didRender();
|
||||||
this.type === CardType.INLINE &&
|
this.toolbarModel?.setDefaultAlign('top');
|
||||||
this.getValue()?.status === 'done'
|
|
||||||
) {
|
|
||||||
this.toolbarModel?.setOffset([-12, 0, -12, 0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.pswp .data-pswp-tool-bar .btn {
|
.pswp .data-pswp-tool-bar .btn {
|
||||||
color: #D9D9D9;
|
color: #f8f9fa;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|
|
@ -42,6 +42,7 @@ class Pswp extends EventEmitter2 implements PswpInterface {
|
||||||
hideAnimationDuration: 0,
|
hideAnimationDuration: 0,
|
||||||
closeOnVerticalDrag: isMobile,
|
closeOnVerticalDrag: isMobile,
|
||||||
tapToClose: true,
|
tapToClose: true,
|
||||||
|
bgOpacity: 0.8,
|
||||||
barsSize: {
|
barsSize: {
|
||||||
top: 44,
|
top: 44,
|
||||||
bottom: 80,
|
bottom: 80,
|
||||||
|
|
|
@ -31,49 +31,9 @@ new Engine(...,{
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
`defaultData`: 默认下拉查询列表展示数据
|
|
||||||
|
|
||||||
`onSearch`: 查询时的方法,或者配置 action,二选其一
|
|
||||||
|
|
||||||
`onSelect`: 选中列表中的一项后回调,这里可以返回一个自定义值与 key、name 一起组合成新的值存在 cardValue 里面。并且执行 getList 命令后会一起返回来
|
|
||||||
|
|
||||||
`onClick`: 在“提及”上单击时触发
|
|
||||||
|
|
||||||
`onMouseEnter`: 鼠标移入“提及”上时触发
|
|
||||||
|
|
||||||
`onRender`: 自定义渲染列表
|
|
||||||
|
|
||||||
`onRenderItem`: 自定义渲染列表项
|
|
||||||
|
|
||||||
`onLoading`: 自定渲染加载状态
|
|
||||||
|
|
||||||
`onEmpty`: 自定渲染空状态
|
|
||||||
|
|
||||||
`action`: 查询地址,始终使用 `GET` 请求,参数 `keyword`
|
|
||||||
|
|
||||||
`data`: 查询时同时将这些数据一起传到到服务端
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
//默认展示的列表数据
|
|
||||||
defaultData?: Array<{ key: string, name: string, avatar?: string}>
|
|
||||||
//查询时的方法,或者配置 action,二选其一
|
|
||||||
onSearch?:(keyword: string) => Promise<Array<{ key: string, name: string, avatar?: string}>>
|
|
||||||
//选中列表中的一项后回调,这里可以返回一个自定义值与 key、name 一起组合成新的值存在 cardValue 里面。并且执行 getList 命令后会一起返回来
|
|
||||||
onSelect?: (data: {[key:string]: string}) => void | {[key: string]: string}
|
|
||||||
//在“提及”上单击事件
|
|
||||||
onClick?:(data: {[key:string]: string}) => void
|
|
||||||
//鼠标移入“提及”上时触发
|
|
||||||
onMouseEnter?:(node: NodeInterface, data: {[key:string]: string}) => void
|
|
||||||
//自定义渲染列表,bindItem 可以为列表项绑定需要的属性和事件
|
|
||||||
onRender?: (data: MentionItem, root: NodeInterface, bindItem: (node: NodeInterface, data: {[key:string]: string}) => NodeInterface) => Promise<string | NodeInterface | void>;
|
|
||||||
//自定义渲染列表项
|
|
||||||
onRenderItem?: (item: MentionItem, root: NodeInterface) => string | NodeInterface | void
|
|
||||||
// 自定渲染加载状态
|
|
||||||
onLoading?: (root: NodeInterface) => string | NodeInterface | void
|
|
||||||
// 自定渲染空状态
|
|
||||||
onEmpty?: (root: NodeInterface) => string | NodeInterface | void
|
|
||||||
/**
|
/**
|
||||||
* 查询地址
|
* 查询地址,或者监听 mention:search 事件执行查询
|
||||||
*/
|
*/
|
||||||
action?: string;
|
action?: string;
|
||||||
/**
|
/**
|
||||||
|
@ -124,3 +84,136 @@ parse?: (
|
||||||
//返回 Array<{ key: string, name: string}>
|
//返回 Array<{ key: string, name: string}>
|
||||||
engine.command.executeMethod('mention', 'getList');
|
engine.command.executeMethod('mention', 'getList');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 插件事件
|
||||||
|
|
||||||
|
`mention:default`: 默认下拉查询列表展示数据
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:default', () => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:search`: 查询时的方法,或者配置 action,二选其一
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:search', (keyword) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
query({ keyword })
|
||||||
|
.then((result) => {
|
||||||
|
resolve(result);
|
||||||
|
})
|
||||||
|
.catch(() => resolve([]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:select`: 选中列表中的一项后回调,这里可以返回一个自定义值与 key、name 一起组合成新的值存在 cardValue 里面。并且执行 getList 命令后会一起返回来
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:select', (data) => {
|
||||||
|
data['test'] = 'test';
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:item-click`: 在“提及”上单击时触发
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on(
|
||||||
|
'mention:item-click',
|
||||||
|
(root: NodeInterface, { key, name }: { key: string; name: string }) => {
|
||||||
|
console.log('mention click:', key, '-', name);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:enter`: 鼠标移入“提及”上时触发
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on(
|
||||||
|
'mention:enter',
|
||||||
|
(layout: NodeInterface, { name }: { key: string; name: string }) => {
|
||||||
|
ReactDOM.render(
|
||||||
|
<div style={{ padding: 5 }}>
|
||||||
|
<p>This is name: {name}</p>
|
||||||
|
<p>配置 mention 插件的 mention:enter 事件</p>
|
||||||
|
<p>此处使用 ReactDOM.render 自定义渲染</p>
|
||||||
|
<p>Use ReactDOM.render to customize rendering here</p>
|
||||||
|
</div>,
|
||||||
|
layout.get<HTMLElement>()!,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:render`: 自定义渲染列表
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on(
|
||||||
|
'mention:render',
|
||||||
|
(
|
||||||
|
root: NodeInterface,
|
||||||
|
data: Array<MentionItem>,
|
||||||
|
bindItem: (
|
||||||
|
node: NodeInterface,
|
||||||
|
data: { [key: string]: string },
|
||||||
|
) => NodeInterface,
|
||||||
|
) => {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
const renderCallback = (items: { [key: string]: Element }) => {
|
||||||
|
// 遍历每个项的DOM节点
|
||||||
|
Object.keys(items).forEach((key) => {
|
||||||
|
const element = items[key];
|
||||||
|
const item = data.find((d) => d.key === key);
|
||||||
|
if (!item) return;
|
||||||
|
// 绑定每个列表项所属的属性、事件,以满足编辑器中上下左右选择的功能需要
|
||||||
|
bindItem($(element), item);
|
||||||
|
});
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
ReactDOM.render(
|
||||||
|
<MentionList data={data} callback={renderCallback} />,
|
||||||
|
root.get<HTMLElement>()!,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:render-item`: 自定义渲染列表项
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:render-item', (data, root) => {
|
||||||
|
const item = $(`<div>${data}</div>`);
|
||||||
|
root.append(item);
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:loading`: 自定渲染加载状态
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:loading', (data, root) => {
|
||||||
|
root.html(`<div>${data}</div>`);
|
||||||
|
// or
|
||||||
|
ReactDOM.render(
|
||||||
|
<div className="data-mention-loading">Loading...</div>,
|
||||||
|
root.get<HTMLElement>()!,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`mention:empty`: 自定渲染空状态
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.engine.on('mention:empty', (root) => {
|
||||||
|
root.html('<div>没有查询到数据</div>');
|
||||||
|
// or
|
||||||
|
ReactDOM.render(
|
||||||
|
<div className="data-mention-empty">Empty</div>,
|
||||||
|
root.get<HTMLElement>()!,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
|
@ -249,7 +249,12 @@ class CollapseComponent implements CollapseComponentInterface {
|
||||||
if (result) body?.append(result);
|
if (result) body?.append(result);
|
||||||
} else if (
|
} else if (
|
||||||
CollapseComponent.render ||
|
CollapseComponent.render ||
|
||||||
(result = this.engine.trigger('mention:render', this.root))
|
(result = this.engine.trigger(
|
||||||
|
'mention:render',
|
||||||
|
this.root,
|
||||||
|
data,
|
||||||
|
this.bindItem,
|
||||||
|
))
|
||||||
) {
|
) {
|
||||||
(CollapseComponent.render
|
(CollapseComponent.render
|
||||||
? CollapseComponent.render(this.root, data, this.bindItem)
|
? CollapseComponent.render(this.root, data, this.bindItem)
|
||||||
|
|
|
@ -41,6 +41,17 @@ new Engine(...,{
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 溢出展示
|
||||||
|
|
||||||
|
```ts
|
||||||
|
overflow?: {
|
||||||
|
// 相对编辑器左侧最大能展示的宽度
|
||||||
|
maxLeftWidth?: () => number;
|
||||||
|
// 相对于编辑器右侧最大能展示的宽度
|
||||||
|
maxRightWidth?: () => number;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
## 命令
|
## 命令
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|
|
@ -251,7 +251,7 @@ class Helper implements HelperInterface {
|
||||||
const $tr = trs.eq(index);
|
const $tr = trs.eq(index);
|
||||||
if (!$tr) return;
|
if (!$tr) return;
|
||||||
let height = parseInt($tr.css('height'));
|
let height = parseInt($tr.css('height'));
|
||||||
height = height || 33;
|
height = height || 35;
|
||||||
$tr.css('height', height + 'px');
|
$tr.css('height', height + 'px');
|
||||||
});
|
});
|
||||||
//补充可编辑器区域
|
//补充可编辑器区域
|
||||||
|
@ -603,7 +603,7 @@ class Helper implements HelperInterface {
|
||||||
const $tr = trs.eq(index);
|
const $tr = trs.eq(index);
|
||||||
if (!$tr) return;
|
if (!$tr) return;
|
||||||
let height = parseInt($tr.css('height'));
|
let height = parseInt($tr.css('height'));
|
||||||
height = height || 33;
|
height = height || 35;
|
||||||
$tr.css('height', height + 'px');
|
$tr.css('height', height + 'px');
|
||||||
});
|
});
|
||||||
return table;
|
return table;
|
||||||
|
|
|
@ -5,9 +5,11 @@ import {
|
||||||
CardType,
|
CardType,
|
||||||
EDITABLE_SELECTOR,
|
EDITABLE_SELECTOR,
|
||||||
isEngine,
|
isEngine,
|
||||||
|
isMobile,
|
||||||
NodeInterface,
|
NodeInterface,
|
||||||
Parser,
|
Parser,
|
||||||
RangeInterface,
|
RangeInterface,
|
||||||
|
removeUnit,
|
||||||
Scrollbar,
|
Scrollbar,
|
||||||
ToolbarItemOptions,
|
ToolbarItemOptions,
|
||||||
} from '@aomao/engine';
|
} from '@aomao/engine';
|
||||||
|
@ -65,7 +67,7 @@ class TableComponent extends Card<TableValue> implements TableInterface {
|
||||||
selection: TableSelectionInterface = new TableSelection(this.editor, this);
|
selection: TableSelectionInterface = new TableSelection(this.editor, this);
|
||||||
conltrollBar: ControllBarInterface = new ControllBar(this.editor, this, {
|
conltrollBar: ControllBarInterface = new ControllBar(this.editor, this, {
|
||||||
col_min_width: 40,
|
col_min_width: 40,
|
||||||
row_min_height: 33,
|
row_min_height: 35,
|
||||||
});
|
});
|
||||||
command: TableCommandInterface = new TableCommand(this.editor, this);
|
command: TableCommandInterface = new TableCommand(this.editor, this);
|
||||||
scrollbar?: Scrollbar;
|
scrollbar?: Scrollbar;
|
||||||
|
@ -80,6 +82,138 @@ class TableComponent extends Card<TableValue> implements TableInterface {
|
||||||
if (isEngine(this.editor)) {
|
if (isEngine(this.editor)) {
|
||||||
this.editor.on('undo', this.doChange);
|
this.editor.on('undo', this.doChange);
|
||||||
this.editor.on('redo', this.doChange);
|
this.editor.on('redo', this.doChange);
|
||||||
|
// tab 键选择
|
||||||
|
if (!this.editor.event.listeners['keydown:tab'])
|
||||||
|
this.editor.event.listeners['keydown:tab'] = [];
|
||||||
|
this.editor.event.listeners['keydown:tab'].unshift(
|
||||||
|
(event: KeyboardEvent) => {
|
||||||
|
if (!isEngine(this.editor)) return;
|
||||||
|
const { change, block, node } = this.editor;
|
||||||
|
|
||||||
|
const range = change.range.get();
|
||||||
|
const td = range.endNode.closest('td');
|
||||||
|
if (td.length === 0) return;
|
||||||
|
const closestBlock = block.closest(range.endNode);
|
||||||
|
if (
|
||||||
|
td.length > 0 &&
|
||||||
|
(block.isLastOffset(range, 'end') ||
|
||||||
|
(closestBlock.name !== 'li' &&
|
||||||
|
node.isEmptyWidthChild(closestBlock)))
|
||||||
|
) {
|
||||||
|
let next = td.next();
|
||||||
|
if (!next) {
|
||||||
|
const nextRow = td.parent()?.next();
|
||||||
|
// 最后一行,最后一列
|
||||||
|
if (!nextRow) {
|
||||||
|
// 新建一行
|
||||||
|
this.command.insertRowDown();
|
||||||
|
next =
|
||||||
|
td
|
||||||
|
.parent()
|
||||||
|
?.next()
|
||||||
|
?.find('td:first-child') || null;
|
||||||
|
} else {
|
||||||
|
next = nextRow.find('td:first-child') || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (next) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.selection.focusCell(next);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// 下键选择
|
||||||
|
this.editor.on('keydown:down', (event) => {
|
||||||
|
if (!isEngine(this.editor)) return;
|
||||||
|
const { change } = this.editor;
|
||||||
|
|
||||||
|
const range = change.range.get();
|
||||||
|
const td = range.endNode.closest('td');
|
||||||
|
if (td.length === 0) return;
|
||||||
|
const contentElement = td.find('.table-main-content');
|
||||||
|
if (!contentElement) return;
|
||||||
|
const tdRect = contentElement
|
||||||
|
.get<HTMLElement>()!
|
||||||
|
.getBoundingClientRect();
|
||||||
|
const rangeRect = range.getBoundingClientRect();
|
||||||
|
if (
|
||||||
|
td.length > 0 &&
|
||||||
|
(rangeRect.bottom === 0 ||
|
||||||
|
tdRect.bottom - rangeRect.bottom < 10)
|
||||||
|
) {
|
||||||
|
const index = td.index();
|
||||||
|
const nextRow = td.parent()?.next();
|
||||||
|
if (nextRow) {
|
||||||
|
let nextIndex = 0;
|
||||||
|
let nextTd = nextRow.find('td:last-child');
|
||||||
|
this.selection.tableModel?.table[nextRow.index()].some(
|
||||||
|
(cell) => {
|
||||||
|
if (
|
||||||
|
!this.helper.isEmptyModelCol(cell) &&
|
||||||
|
nextIndex >= index &&
|
||||||
|
cell.element
|
||||||
|
) {
|
||||||
|
nextTd = $(cell.element);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
nextIndex++;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (nextTd) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.selection.focusCell(nextTd, true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 上键选择
|
||||||
|
this.editor.on('keydown:up', (event) => {
|
||||||
|
if (!isEngine(this.editor)) return;
|
||||||
|
const { change } = this.editor;
|
||||||
|
|
||||||
|
const range = change.range.get();
|
||||||
|
const td = range.endNode.closest('td');
|
||||||
|
if (td.length === 0) return;
|
||||||
|
const contentElement = td.find('.table-main-content');
|
||||||
|
if (!contentElement) return;
|
||||||
|
const tdRect = contentElement
|
||||||
|
.get<HTMLElement>()!
|
||||||
|
.getBoundingClientRect();
|
||||||
|
const rangeRect = range.getBoundingClientRect();
|
||||||
|
if (
|
||||||
|
td.length > 0 &&
|
||||||
|
(rangeRect.top === 0 || rangeRect.top - tdRect.top < 10)
|
||||||
|
) {
|
||||||
|
const index = td.index();
|
||||||
|
const prevRow = td.parent()?.prev();
|
||||||
|
if (prevRow) {
|
||||||
|
let prevIndex = 0;
|
||||||
|
let prevTd = prevRow.find('td:first-child');
|
||||||
|
this.selection.tableModel?.table[prevRow.index()].some(
|
||||||
|
(cell) => {
|
||||||
|
if (
|
||||||
|
!this.helper.isEmptyModelCol(cell) &&
|
||||||
|
prevIndex >= index &&
|
||||||
|
cell.element
|
||||||
|
) {
|
||||||
|
prevTd = $(cell.element);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
prevIndex++;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (prevTd) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.selection.focusCell(prevTd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (this.colorTool) return;
|
if (this.colorTool) return;
|
||||||
this.colorTool = new ColorTool(this.editor, this.id, {
|
this.colorTool = new ColorTool(this.editor, this.id, {
|
||||||
|
@ -172,10 +306,7 @@ class TableComponent extends Card<TableValue> implements TableInterface {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
if (this.isMaximize) return funBtns;
|
if (this.isMaximize) return funBtns;
|
||||||
return [
|
const toolbars: Array<ToolbarItemOptions | CardToolbarItemOptions> = [
|
||||||
{
|
|
||||||
type: 'dnd',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'maximize',
|
type: 'maximize',
|
||||||
},
|
},
|
||||||
|
@ -190,6 +321,12 @@ class TableComponent extends Card<TableValue> implements TableInterface {
|
||||||
},
|
},
|
||||||
...funBtns,
|
...funBtns,
|
||||||
];
|
];
|
||||||
|
if (removeUnit(this.wrapper?.css('margin-left') || '0') === 0) {
|
||||||
|
toolbars.unshift({
|
||||||
|
type: 'dnd',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return toolbars;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAlign(event: MouseEvent, align: 'top' | 'middle' | 'bottom' = 'top') {
|
updateAlign(event: MouseEvent, align: 'top' | 'middle' | 'bottom' = 'top') {
|
||||||
|
@ -291,12 +428,10 @@ class TableComponent extends Card<TableValue> implements TableInterface {
|
||||||
super.activate(activated);
|
super.activate(activated);
|
||||||
if (activated) {
|
if (activated) {
|
||||||
this.wrapper?.addClass('active');
|
this.wrapper?.addClass('active');
|
||||||
this.scrollbar?.enableScroll();
|
|
||||||
} else {
|
} else {
|
||||||
this.selection.clearSelect();
|
this.selection.clearSelect();
|
||||||
this.conltrollBar.hideContextMenu();
|
this.conltrollBar.hideContextMenu();
|
||||||
this.wrapper?.removeClass('active');
|
this.wrapper?.removeClass('active');
|
||||||
this.scrollbar?.disableScroll();
|
|
||||||
}
|
}
|
||||||
this.scrollbar?.refresh();
|
this.scrollbar?.refresh();
|
||||||
}
|
}
|
||||||
|
@ -344,6 +479,33 @@ class TableComponent extends Card<TableValue> implements TableInterface {
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
overflow(max: number) {
|
||||||
|
// 表格宽度
|
||||||
|
const tableWidth = this.wrapper?.find('.data-table')?.width() || 0;
|
||||||
|
const rootWidth = this.getCenter().width();
|
||||||
|
// 溢出的宽度
|
||||||
|
const overflowWidth = tableWidth - rootWidth;
|
||||||
|
if (overflowWidth > 0) {
|
||||||
|
this.wrapper?.css(
|
||||||
|
'margin-right',
|
||||||
|
`-${overflowWidth > max ? max : overflowWidth}px`,
|
||||||
|
);
|
||||||
|
} else if (overflowWidth < 0) {
|
||||||
|
this.wrapper?.css('margin-right', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateScrollbar = () => {
|
||||||
|
if (!this.scrollbar) return;
|
||||||
|
const hideHeight =
|
||||||
|
(this.wrapper?.getBoundingClientRect()?.bottom || 0) -
|
||||||
|
(this.wrapper?.getViewport().bottom || 0);
|
||||||
|
console.log(hideHeight);
|
||||||
|
this.wrapper?.find('.data-scrollbar-x').css({
|
||||||
|
bottom: `${hideHeight > 0 ? hideHeight + 2 : 0}px`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
didRender() {
|
didRender() {
|
||||||
super.didRender();
|
super.didRender();
|
||||||
this.viewport = isEngine(this.editor)
|
this.viewport = isEngine(this.editor)
|
||||||
|
@ -356,8 +518,49 @@ class TableComponent extends Card<TableValue> implements TableInterface {
|
||||||
if (!isEngine(this.editor) || this.editor.readonly)
|
if (!isEngine(this.editor) || this.editor.readonly)
|
||||||
this.toolbarModel?.setOffset([0, 0]);
|
this.toolbarModel?.setOffset([0, 0]);
|
||||||
else this.toolbarModel?.setOffset([0, -28, 0, -6]);
|
else this.toolbarModel?.setOffset([0, -28, 0, -6]);
|
||||||
|
const tablePlugin = this.editor.plugin.components['table'];
|
||||||
|
const tableOptions = tablePlugin?.options['overflow'] || {};
|
||||||
if (this.viewport) {
|
if (this.viewport) {
|
||||||
this.scrollbar = new Scrollbar(this.viewport, true, false, true);
|
const overflowLeftConfig = tableOptions['maxLeftWidth']
|
||||||
|
? {
|
||||||
|
onScrollX: (x: number) => {
|
||||||
|
const max = tableOptions['maxLeftWidth']();
|
||||||
|
this.wrapper?.css(
|
||||||
|
'margin-left',
|
||||||
|
`-${x > max ? max : x}px`,
|
||||||
|
);
|
||||||
|
if (x > 0) {
|
||||||
|
this.editor.root.find('.data-card-dnd').hide();
|
||||||
|
} else {
|
||||||
|
this.editor.root.find('.data-card-dnd').show();
|
||||||
|
}
|
||||||
|
return x - max;
|
||||||
|
},
|
||||||
|
getScrollLeft: (left: number) => {
|
||||||
|
return (
|
||||||
|
left -
|
||||||
|
removeUnit(
|
||||||
|
this.wrapper?.css('margin-left') || '0',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getOffsetWidth: (width: number) => {
|
||||||
|
return (
|
||||||
|
width +
|
||||||
|
removeUnit(
|
||||||
|
this.wrapper?.css('margin-left') || '0',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
this.scrollbar = new Scrollbar(
|
||||||
|
this.viewport,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
overflowLeftConfig,
|
||||||
|
);
|
||||||
this.scrollbar.setContentNode(this.viewport.find('.data-table')!);
|
this.scrollbar.setContentNode(this.viewport.find('.data-table')!);
|
||||||
this.scrollbar.on('display', (display: 'node' | 'block') => {
|
this.scrollbar.on('display', (display: 'node' | 'block') => {
|
||||||
if (display === 'block') {
|
if (display === 'block') {
|
||||||
|
@ -367,14 +570,18 @@ class TableComponent extends Card<TableValue> implements TableInterface {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.scrollbar.disableScroll();
|
this.scrollbar.disableScroll();
|
||||||
let changeTimeout: NodeJS.Timeout | undefined;
|
|
||||||
const handleScrollbarChange = () => {
|
const handleScrollbarChange = () => {
|
||||||
if (changeTimeout) clearTimeout(changeTimeout);
|
if (tableOptions['maxRightWidth'])
|
||||||
changeTimeout = setTimeout(() => {
|
this.overflow(tableOptions['maxRightWidth']());
|
||||||
if (isEngine(this.editor)) this.editor.ot.initSelection();
|
if (isEngine(this.editor)) this.editor.ot.initSelection();
|
||||||
}, 50);
|
|
||||||
};
|
};
|
||||||
this.scrollbar.on('change', handleScrollbarChange);
|
this.scrollbar.on('change', handleScrollbarChange);
|
||||||
|
if (!isMobile)
|
||||||
|
window.addEventListener('scroll', this.updateScrollbar);
|
||||||
|
window.addEventListener('resize', this.updateScrollbar);
|
||||||
|
if (isEngine(this.editor) && !isMobile) {
|
||||||
|
this.editor.scrollNode?.on('scroll', this.updateScrollbar);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.selection.on('select', () => {
|
this.selection.on('select', () => {
|
||||||
this.conltrollBar.refresh();
|
this.conltrollBar.refresh();
|
||||||
|
@ -401,6 +608,8 @@ class TableComponent extends Card<TableValue> implements TableInterface {
|
||||||
if (!silence) {
|
if (!silence) {
|
||||||
this.onChange();
|
this.onChange();
|
||||||
}
|
}
|
||||||
|
if (tableOptions['maxRightWidth'])
|
||||||
|
this.overflow(tableOptions['maxRightWidth']());
|
||||||
this.scrollbar?.refresh();
|
this.scrollbar?.refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -412,6 +621,9 @@ class TableComponent extends Card<TableValue> implements TableInterface {
|
||||||
if (tableValue) this.setValue(tableValue);
|
if (tableValue) this.setValue(tableValue);
|
||||||
this.onChange();
|
this.onChange();
|
||||||
}
|
}
|
||||||
|
if (tableOptions['maxRightWidth'])
|
||||||
|
this.overflow(tableOptions['maxRightWidth']());
|
||||||
|
this.scrollbar?.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -458,7 +458,7 @@ class TableSelection extends EventEmitter2 implements TableSelectionInterface {
|
||||||
this.emit('select', this.selectArea);
|
this.emit('select', this.selectArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
focusCell(cell: NodeInterface | Node) {
|
focusCell(cell: NodeInterface | Node, start: boolean = false) {
|
||||||
if (!isEngine(this.editor)) return;
|
if (!isEngine(this.editor)) return;
|
||||||
const { change } = this.editor;
|
const { change } = this.editor;
|
||||||
if (isNode(cell)) cell = $(cell);
|
if (isNode(cell)) cell = $(cell);
|
||||||
|
@ -469,7 +469,7 @@ class TableSelection extends EventEmitter2 implements TableSelectionInterface {
|
||||||
.select(editableElement, true)
|
.select(editableElement, true)
|
||||||
.shrinkToElementNode()
|
.shrinkToElementNode()
|
||||||
.shrinkToTextNode()
|
.shrinkToTextNode()
|
||||||
.collapse(false);
|
.collapse(start);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
change.range.select(range);
|
change.range.select(range);
|
||||||
}, 20);
|
}, 20);
|
||||||
|
@ -1090,9 +1090,10 @@ class TableSelection extends EventEmitter2 implements TableSelectionInterface {
|
||||||
top += rect.top - (vRect?.top || 0) - 13;
|
top += rect.top - (vRect?.top || 0) - 13;
|
||||||
left += rect.left - (vRect?.left || 0);
|
left += rect.left - (vRect?.left || 0);
|
||||||
}
|
}
|
||||||
const sLeft = removeUnit(
|
const sLeft =
|
||||||
this.table.wrapper?.find('.data-scrollbar')?.css('left') || '0',
|
removeUnit(
|
||||||
);
|
this.table.wrapper?.find('.data-scrollbar')?.css('left') || '0',
|
||||||
|
) + removeUnit(this.table.wrapper?.css('margin-left') || '0');
|
||||||
left += sLeft;
|
left += sLeft;
|
||||||
|
|
||||||
const headerHeight =
|
const headerHeight =
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
const TABLE_WRAPPER_CLASS_NAME = 'table-wrapper';
|
const TABLE_WRAPPER_CLASS_NAME = 'table-wrapper';
|
||||||
|
const TABLE_OVERFLOW_CLASS_NAME = 'table-overflow';
|
||||||
const TABLE_CLASS_NAME = 'data-table';
|
const TABLE_CLASS_NAME = 'data-table';
|
||||||
const COLS_HEADER_CLASS_NAME = 'table-cols-header';
|
const COLS_HEADER_CLASS_NAME = 'table-cols-header';
|
||||||
const COLS_HEADER_ITEM_CLASS_NAME = 'table-cols-header-item';
|
const COLS_HEADER_ITEM_CLASS_NAME = 'table-cols-header-item';
|
||||||
|
@ -41,6 +42,7 @@ const TABLE_TD_BG_CLASS_NAME = 'table-main-bg';
|
||||||
|
|
||||||
class Template implements TemplateInterface {
|
class Template implements TemplateInterface {
|
||||||
static readonly TABLE_WRAPPER_CLASS = `.${TABLE_WRAPPER_CLASS_NAME}`;
|
static readonly TABLE_WRAPPER_CLASS = `.${TABLE_WRAPPER_CLASS_NAME}`;
|
||||||
|
static readonly TABLE_OVERFLOW_CLASS = `.${TABLE_OVERFLOW_CLASS_NAME}`;
|
||||||
static readonly TABLE_CLASS = `.${TABLE_CLASS_NAME}`;
|
static readonly TABLE_CLASS = `.${TABLE_CLASS_NAME}`;
|
||||||
static readonly COLS_HEADER_CLASS = `.${COLS_HEADER_CLASS_NAME}`;
|
static readonly COLS_HEADER_CLASS = `.${COLS_HEADER_CLASS_NAME}`;
|
||||||
static readonly COLS_HEADER_ITEM_CLASS = `.${COLS_HEADER_ITEM_CLASS_NAME}`;
|
static readonly COLS_HEADER_ITEM_CLASS = `.${COLS_HEADER_ITEM_CLASS_NAME}`;
|
||||||
|
@ -122,7 +124,7 @@ class Template implements TemplateInterface {
|
||||||
* @return {string} 返回 html 字符串
|
* @return {string} 返回 html 字符串
|
||||||
*/
|
*/
|
||||||
htmlEdit(
|
htmlEdit(
|
||||||
{ rows, cols, html, noBorder }: TableValue,
|
{ rows, cols, html, noBorder, overflow }: TableValue,
|
||||||
menus: TableMenu,
|
menus: TableMenu,
|
||||||
): string {
|
): string {
|
||||||
cols = cols === -Infinity ? 1 : cols;
|
cols = cols === -Infinity ? 1 : cols;
|
||||||
|
@ -188,7 +190,9 @@ class Template implements TemplateInterface {
|
||||||
noBorder === true ? " data-table-no-border='true'" : ''
|
noBorder === true ? " data-table-no-border='true'" : ''
|
||||||
} ${DATA_TRANSIENT_ATTRIBUTES}="class">${colgroup}${trs}</table>`;
|
} ${DATA_TRANSIENT_ATTRIBUTES}="class">${colgroup}${trs}</table>`;
|
||||||
|
|
||||||
return `<div class="${TABLE_WRAPPER_CLASS_NAME}" ${DATA_TRANSIENT_ATTRIBUTES}="*">${tableHeader}<div class="${VIEWPORT}">${this.renderColsHeader(
|
return `<div class="${TABLE_WRAPPER_CLASS_NAME} ${
|
||||||
|
overflow !== false ? TABLE_OVERFLOW_CLASS_NAME : ''
|
||||||
|
}" ${DATA_TRANSIENT_ATTRIBUTES}="*">${tableHeader}<div class="${VIEWPORT}">${this.renderColsHeader(
|
||||||
cols,
|
cols,
|
||||||
)}${table}${placeholder}${tableHighlight}</div>${this.renderRowsHeader(
|
)}${table}${placeholder}${tableHighlight}</div>${this.renderRowsHeader(
|
||||||
rows,
|
rows,
|
||||||
|
|
|
@ -27,7 +27,7 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-table tr {
|
.data-table tr {
|
||||||
height: 33px;
|
height: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-table tr td {
|
.data-table tr td {
|
||||||
|
@ -67,7 +67,7 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper.scrollbar-show {
|
.table-wrapper.scrollbar-show {
|
||||||
margin-bottom: -8px;
|
margin-bottom: -10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper.data-table-highlight tr td[table-cell-selection]:after {
|
.table-wrapper.data-table-highlight tr td[table-cell-selection]:after {
|
||||||
|
@ -98,9 +98,8 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper.data-table-highlight-all .table-header {
|
.table-wrapper.data-table-highlight-all .table-header .table-header-item {
|
||||||
background: rgba(255, 77, 79, 0.4) !important;
|
background: rgba(255, 77, 79, 0.4) !important;
|
||||||
border-color: rgba(255, 77, 79, 0.4) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper .table-header-item:hover{
|
.table-wrapper .table-header-item:hover{
|
||||||
|
@ -113,16 +112,15 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
|
|
||||||
.table-wrapper .table-header.selected .table-header-item {
|
.table-wrapper .table-header.selected .table-header-item {
|
||||||
background: #4daaff;
|
background: #4daaff;
|
||||||
|
border-color: #4daaff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper .table-cols-header {
|
.table-wrapper .table-cols-header {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 14px;
|
height: 13px;
|
||||||
display: none;
|
display: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
margin-bottom: -1px;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper.active .table-cols-header {
|
.table-wrapper.active .table-cols-header {
|
||||||
|
@ -131,7 +129,7 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
|
|
||||||
.table-wrapper .table-cols-header .table-cols-header-item {
|
.table-wrapper .table-cols-header .table-cols-header-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 14px;
|
height: 13px;
|
||||||
width: auto;
|
width: auto;
|
||||||
border: 1px solid #dfdfdf;
|
border: 1px solid #dfdfdf;
|
||||||
border-bottom: 0 none;
|
border-bottom: 0 none;
|
||||||
|
@ -161,7 +159,6 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
background: #fff;
|
background: #fff;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
height: 14px;
|
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
|
@ -250,11 +247,11 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
width: 14px;
|
width: 14px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
visibility: hidden;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper.active .table-rows-header {
|
.table-wrapper.active .table-rows-header {
|
||||||
visibility: visible;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper .table-rows-header .table-rows-header-item {
|
.table-wrapper .table-rows-header .table-rows-header-item {
|
||||||
|
@ -387,7 +384,7 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
|
|
||||||
.table-wrapper .table-viewport .scrollbar-shadow-left {
|
.table-wrapper .table-viewport .scrollbar-shadow-left {
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 8px;
|
bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper.active .table-viewport .scrollbar-shadow-left {
|
.table-wrapper.active .table-viewport .scrollbar-shadow-left {
|
||||||
|
@ -397,7 +394,7 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
|
|
||||||
.table-wrapper .table-viewport .scrollbar-shadow-right {
|
.table-wrapper .table-viewport .scrollbar-shadow-right {
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 8px;
|
bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper.active .table-viewport .scrollbar-shadow-right {
|
.table-wrapper.active .table-viewport .scrollbar-shadow-right {
|
||||||
|
@ -469,6 +466,10 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-wrapper .table-main-content * {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.table-wrapper .table-main-bg {
|
.table-wrapper .table-main-bg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -573,7 +574,7 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper.scrollbar-show .data-scrollable.scroll-x {
|
.table-wrapper.scrollbar-show .data-scrollable.scroll-x {
|
||||||
padding-bottom: 8px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper .data-scrollable.scroll-x {
|
.table-wrapper .data-scrollable.scroll-x {
|
||||||
|
@ -584,20 +585,17 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper.scrollbar-show .data-scrollable.scroll-x:hover {
|
|
||||||
/**overflow-x: auto;**/
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-wrapper.scrollbar-show .data-scrollable.scroll-x .data-scrollbar-x{
|
.table-wrapper.scrollbar-show .data-scrollable.scroll-x .data-scrollbar-x{
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper .data-scrollable .data-scrollbar.data-scrollbar-x {
|
.table-wrapper .data-scrollable .data-scrollbar.data-scrollbar-x {
|
||||||
height: 4px;
|
height: 6px;
|
||||||
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper .data-scrollable .data-scrollbar.data-scrollbar-x .data-scrollbar-trigger {
|
.table-wrapper .data-scrollable .data-scrollbar.data-scrollbar-x .data-scrollbar-trigger {
|
||||||
height: 4px;
|
height: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper .table-rows-header .table-row-delete-button,.table-wrapper .table-rows-header .table-row-add-button {
|
.table-wrapper .table-rows-header .table-row-delete-button,.table-wrapper .table-rows-header .table-row-add-button {
|
||||||
|
@ -697,11 +695,11 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-table-reader.data-scrollable.scroll-x {
|
.data-table-reader.data-scrollable.scroll-x {
|
||||||
padding-bottom: 8px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-table-reader .scrollbar-shadow-left, .data-table-reader .scrollbar-shadow-right {
|
.data-table-reader .scrollbar-shadow-left, .data-table-reader .scrollbar-shadow-right {
|
||||||
bottom: 8px;
|
bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-table-reader.scrollbar-show.data-scrollable.scroll-x .data-scrollbar-x{
|
.data-table-reader.scrollbar-show.data-scrollable.scroll-x .data-scrollbar-x{
|
||||||
|
@ -709,14 +707,20 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-table-reader.data-scrollable .data-scrollbar.data-scrollbar-x {
|
.data-table-reader.data-scrollable .data-scrollbar.data-scrollbar-x {
|
||||||
height: 4px;
|
height: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-table-reader.data-scrollable .data-scrollbar.data-scrollbar-x .data-scrollbar-trigger {
|
.data-table-reader.data-scrollable .data-scrollbar.data-scrollbar-x .data-scrollbar-trigger {
|
||||||
height: 4px;
|
height: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-card-key="table"].data-card-block-max > [data-card-element="body"] > [data-card-element="center"] {
|
[data-card-key="table"].data-card-block-max > [data-card-element="body"] > [data-card-element="center"] {
|
||||||
padding: 48px;
|
padding: 48px;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
表格可溢出样式
|
||||||
|
**/
|
||||||
|
.table-wrapper.table-overflow {
|
||||||
|
width: auto;
|
||||||
|
}
|
|
@ -21,6 +21,10 @@ import { TableInterface } from './types';
|
||||||
|
|
||||||
export interface Options extends PluginOptions {
|
export interface Options extends PluginOptions {
|
||||||
hotkey?: string | Array<string>;
|
hotkey?: string | Array<string>;
|
||||||
|
overflow?: {
|
||||||
|
maxLeftWidth?: () => number;
|
||||||
|
maxRightWidth?: () => number;
|
||||||
|
};
|
||||||
markdown?: boolean;
|
markdown?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,6 +269,7 @@ class Table extends Plugin<Options> {
|
||||||
this.editor.card.insert(TableComponent.cardName, {
|
this.editor.card.insert(TableComponent.cardName, {
|
||||||
rows: rows || 3,
|
rows: rows || 3,
|
||||||
cols: cols || 3,
|
cols: cols || 3,
|
||||||
|
overflow: this.options.overflow,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,7 @@ export type TableValue = {
|
||||||
html?: string;
|
html?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
noBorder?: boolean;
|
noBorder?: boolean;
|
||||||
|
overflow?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TableMenuItem = {
|
export type TableMenuItem = {
|
||||||
|
@ -367,7 +368,7 @@ export interface TableSelectionInterface extends EventEmitter2 {
|
||||||
|
|
||||||
hideHighlight(): void;
|
hideHighlight(): void;
|
||||||
|
|
||||||
focusCell(cell: NodeInterface | Node): void;
|
focusCell(cell: NodeInterface | Node, start?: boolean): void;
|
||||||
|
|
||||||
selectCellRange(cell: NodeInterface | Node): void;
|
selectCellRange(cell: NodeInterface | Node): void;
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,14 @@ new Engine(...,{
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 是否显示视频标题
|
||||||
|
|
||||||
|
默认显示
|
||||||
|
|
||||||
|
```ts
|
||||||
|
showTitle?: boolean
|
||||||
|
```
|
||||||
|
|
||||||
### 文件上传
|
### 文件上传
|
||||||
|
|
||||||
`action`: 上传地址,始终使用 `POST` 请求
|
`action`: 上传地址,始终使用 `POST` 请求
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
[data-card-key="video"] {
|
.data-video {
|
||||||
outline: 1px solid #ddd;
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.data-video-content {
|
.data-video-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -9,6 +11,8 @@
|
||||||
.data-video-content video {
|
.data-video-content video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
.data-video-uploading,
|
.data-video-uploading,
|
||||||
.data-video-uploaded,
|
.data-video-uploaded,
|
||||||
|
@ -23,7 +27,7 @@
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
.data-video-active {
|
.data-video-active {
|
||||||
outline: 1px solid #d9d9d9;
|
user-select: none;
|
||||||
}
|
}
|
||||||
.data-video-center {
|
.data-video-center {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -81,3 +85,20 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin: -2px 5px 0 0;
|
margin: -2px 5px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.data-video-title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.data-video-title::selection {
|
||||||
|
background: transparent
|
||||||
|
}
|
||||||
|
.data-video .data-resizer {
|
||||||
|
z-index: inherit;
|
||||||
|
}
|
||||||
|
.data-video .data-resizer .data-resizer-holder {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-video .data-resizing {
|
||||||
|
z-index: 3;
|
||||||
|
}
|
|
@ -1,16 +1,18 @@
|
||||||
import { Tooltip } from '@aomao/engine';
|
import type {
|
||||||
|
CardToolbarItemOptions,
|
||||||
|
ToolbarItemOptions,
|
||||||
|
NodeInterface,
|
||||||
|
ResizerInterface,
|
||||||
|
} from '@aomao/engine';
|
||||||
import {
|
import {
|
||||||
$,
|
$,
|
||||||
Card,
|
Card,
|
||||||
CardToolbarItemOptions,
|
|
||||||
CardType,
|
CardType,
|
||||||
escape,
|
escape,
|
||||||
getFileSize,
|
getFileSize,
|
||||||
isEngine,
|
isEngine,
|
||||||
isMobile,
|
|
||||||
NodeInterface,
|
|
||||||
sanitizeUrl,
|
sanitizeUrl,
|
||||||
ToolbarItemOptions,
|
Resizer,
|
||||||
} from '@aomao/engine';
|
} from '@aomao/engine';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
|
@ -49,6 +51,22 @@ export type VideoValue = {
|
||||||
* 视频大小
|
* 视频大小
|
||||||
*/
|
*/
|
||||||
size?: number;
|
size?: number;
|
||||||
|
/**
|
||||||
|
* 宽度
|
||||||
|
*/
|
||||||
|
width?: number;
|
||||||
|
/**
|
||||||
|
* 高度
|
||||||
|
*/
|
||||||
|
height?: number;
|
||||||
|
/**
|
||||||
|
* 真实宽度
|
||||||
|
*/
|
||||||
|
naturalWidth?: number;
|
||||||
|
/**
|
||||||
|
* 真实高度
|
||||||
|
*/
|
||||||
|
naturalHeight?: number;
|
||||||
/**
|
/**
|
||||||
* 错误状态下的错误信息
|
* 错误状态下的错误信息
|
||||||
*/
|
*/
|
||||||
|
@ -56,6 +74,14 @@ export type VideoValue = {
|
||||||
};
|
};
|
||||||
|
|
||||||
class VideoComponent extends Card<VideoValue> {
|
class VideoComponent extends Card<VideoValue> {
|
||||||
|
maxWidth: number = 0;
|
||||||
|
resizer?: ResizerInterface;
|
||||||
|
video?: NodeInterface;
|
||||||
|
rate: number = 1;
|
||||||
|
isLoad: boolean = false;
|
||||||
|
container?: NodeInterface;
|
||||||
|
videoContainer?: NodeInterface;
|
||||||
|
title?: NodeInterface;
|
||||||
static get cardName() {
|
static get cardName() {
|
||||||
return 'video';
|
return 'video';
|
||||||
}
|
}
|
||||||
|
@ -68,7 +94,9 @@ class VideoComponent extends Card<VideoValue> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private container?: NodeInterface;
|
static get singleSelectable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
getLocales() {
|
getLocales() {
|
||||||
return this.editor.language.get<{ [key: string]: string }>('video');
|
return this.editor.language.get<{ [key: string]: string }>('video');
|
||||||
|
@ -102,7 +130,9 @@ class VideoComponent extends Card<VideoValue> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileSize: string = size ? getFileSize(size) : '';
|
const fileSize: string = size ? getFileSize(size) : '';
|
||||||
|
const titleElement = name
|
||||||
|
? `<div class="data-video-title">${escape(name)}</div>`
|
||||||
|
: '';
|
||||||
if (status === 'uploading') {
|
if (status === 'uploading') {
|
||||||
return `
|
return `
|
||||||
<div class="data-video">
|
<div class="data-video">
|
||||||
|
@ -143,10 +173,11 @@ class VideoComponent extends Card<VideoValue> {
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
const videoPlugin = this.editor.plugin.components['video'];
|
||||||
return `
|
return `
|
||||||
<div class="data-video">
|
<div class="data-video">
|
||||||
<div class="data-video-content data-video-done"></div>
|
<div class="data-video-content data-video-done"></div>
|
||||||
|
${videoPlugin && videoPlugin.options.showTitle !== false ? titleElement : ''}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -176,12 +207,18 @@ class VideoComponent extends Card<VideoValue> {
|
||||||
if (cover) {
|
if (cover) {
|
||||||
video.poster = sanitizeUrl(this.onBeforeRender('cover', cover));
|
video.poster = sanitizeUrl(this.onBeforeRender('cover', cover));
|
||||||
}
|
}
|
||||||
|
this.maxWidth = this.getMaxWidth();
|
||||||
|
if (value.naturalHeight && value.naturalWidth)
|
||||||
|
this.rate = value.naturalHeight / value.naturalWidth;
|
||||||
this.container?.find('.data-video-content').append(video);
|
this.container?.find('.data-video-content').append(video);
|
||||||
|
this.videoContainer = this.container?.find('.data-video-content');
|
||||||
video.oncontextmenu = function () {
|
video.oncontextmenu = function () {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.video = $(video);
|
||||||
|
this.title = this.container?.find('.data-video-title');
|
||||||
|
this.resetSize();
|
||||||
// 一次渲染时序开启 controls 会触发一次内容为空的 window.onerror,疑似 chrome bug
|
// 一次渲染时序开启 controls 会触发一次内容为空的 window.onerror,疑似 chrome bug
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
video.controls = true;
|
video.controls = true;
|
||||||
|
@ -234,9 +271,158 @@ class VideoComponent extends Card<VideoValue> {
|
||||||
this.container?.find('.percent').html(`${percent}%`);
|
this.container?.find('.percent').html(`${percent}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMaxWidth(node: NodeInterface = this.getCenter()) {
|
||||||
|
const block = this.editor.block.closest(node).get<HTMLElement>();
|
||||||
|
if (!block) return 0;
|
||||||
|
return block.clientWidth - 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置大小
|
||||||
|
*/
|
||||||
|
resetSize() {
|
||||||
|
if (!this.videoContainer) return;
|
||||||
|
const value = this.getValue();
|
||||||
|
if (!value) return;
|
||||||
|
this.videoContainer.css({
|
||||||
|
width: '',
|
||||||
|
//height: '',
|
||||||
|
});
|
||||||
|
this.container?.css({
|
||||||
|
width: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const video = this.video?.get<HTMLVideoElement>();
|
||||||
|
if (!video) return;
|
||||||
|
let { width, height, naturalWidth, naturalHeight } = value;
|
||||||
|
if (!naturalWidth) {
|
||||||
|
naturalWidth = video.videoWidth;
|
||||||
|
}
|
||||||
|
if (!naturalHeight) {
|
||||||
|
naturalHeight = video.videoHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!height) {
|
||||||
|
width = naturalWidth;
|
||||||
|
height = Math.round(this.rate * width);
|
||||||
|
} else if (!width) {
|
||||||
|
height = naturalHeight;
|
||||||
|
width = Math.round(height / this.rate);
|
||||||
|
} else if (width && height) {
|
||||||
|
// 修正非正常的比例
|
||||||
|
height = Math.round(this.rate * width);
|
||||||
|
} else {
|
||||||
|
width = naturalWidth;
|
||||||
|
height = naturalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width > this.maxWidth) {
|
||||||
|
width = this.maxWidth;
|
||||||
|
height = Math.round(width * this.rate);
|
||||||
|
}
|
||||||
|
this.container?.css({
|
||||||
|
width: `${width}px`,
|
||||||
|
});
|
||||||
|
this.videoContainer.css('width', `${width}px`);
|
||||||
|
//this.videoContainer.css('height', `${height}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeSize(width: number, height: number) {
|
||||||
|
if (width < 24) {
|
||||||
|
width = 24;
|
||||||
|
height = width * this.rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width > this.maxWidth) {
|
||||||
|
width = this.maxWidth;
|
||||||
|
height = width * this.rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height < 24) {
|
||||||
|
height = 24;
|
||||||
|
width = height / this.rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
width = Math.round(width);
|
||||||
|
height = Math.round(height);
|
||||||
|
this.videoContainer?.css({
|
||||||
|
width: `${width}px`,
|
||||||
|
//height: `${height}px`,
|
||||||
|
});
|
||||||
|
this.container?.css({
|
||||||
|
width: `${width}px`,
|
||||||
|
});
|
||||||
|
this.setValue({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
this.resizer?.destroy();
|
||||||
|
this.initResizer();
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowResize = () => {
|
||||||
|
if (!isEngine(this.editor)) return;
|
||||||
|
this.maxWidth = this.getMaxWidth();
|
||||||
|
this.resetSize();
|
||||||
|
|
||||||
|
if (this.resizer) {
|
||||||
|
this.resizer.maxWidth = this.maxWidth;
|
||||||
|
this.resizer.setSize(
|
||||||
|
this.videoContainer?.width() || 0,
|
||||||
|
this.videoContainer?.height() || 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initResizer() {
|
||||||
|
const value = this.getValue();
|
||||||
|
if (!value) return;
|
||||||
|
const { naturalHeight, naturalWidth, status } = value;
|
||||||
|
if (!naturalHeight || !naturalWidth || status !== 'done') return;
|
||||||
|
const { width, height, cover } = value;
|
||||||
|
this.maxWidth = this.getMaxWidth();
|
||||||
|
this.rate = naturalHeight / naturalWidth;
|
||||||
|
window.removeEventListener('resize', this.onWindowResize);
|
||||||
|
window.addEventListener('resize', this.onWindowResize);
|
||||||
|
// 拖动调整视频大小
|
||||||
|
const resizer = new Resizer({
|
||||||
|
imgUrl: cover,
|
||||||
|
width: width || naturalWidth,
|
||||||
|
height: height || naturalHeight,
|
||||||
|
rate: this.rate,
|
||||||
|
maxWidth: this.maxWidth,
|
||||||
|
onChange: ({ width, height }) => this.changeSize(width, height),
|
||||||
|
});
|
||||||
|
this.resizer = resizer;
|
||||||
|
const resizerNode = resizer.render();
|
||||||
|
this.videoContainer?.append(resizerNode);
|
||||||
|
}
|
||||||
|
|
||||||
onActivate(activated: boolean) {
|
onActivate(activated: boolean) {
|
||||||
if (activated) this.container?.addClass('data-video-active');
|
if (activated) {
|
||||||
else this.container?.removeClass('data-video-active');
|
this.container?.addClass('data-video-active');
|
||||||
|
this.initResizer();
|
||||||
|
} else {
|
||||||
|
this.container?.removeClass('data-video-active');
|
||||||
|
this.resizer?.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectByOther(
|
||||||
|
selected: boolean,
|
||||||
|
value?: {
|
||||||
|
color: string;
|
||||||
|
rgb: string;
|
||||||
|
},
|
||||||
|
): NodeInterface | void {
|
||||||
|
this.container?.css(
|
||||||
|
'outline',
|
||||||
|
selected ? '2px solid ' + value!.color : '',
|
||||||
|
);
|
||||||
|
const className = 'card-selected-other';
|
||||||
|
if (selected) this.root.addClass(className);
|
||||||
|
else this.root.removeClass(className);
|
||||||
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
checker(
|
checker(
|
||||||
|
@ -284,6 +470,8 @@ class VideoComponent extends Card<VideoValue> {
|
||||||
const { command, plugin } = this.editor;
|
const { command, plugin } = this.editor;
|
||||||
const { video_id, status } = value;
|
const { video_id, status } = value;
|
||||||
const locales = this.getLocales();
|
const locales = this.getLocales();
|
||||||
|
|
||||||
|
this.maxWidth = this.getMaxWidth();
|
||||||
//阅读模式
|
//阅读模式
|
||||||
if (!isEngine(this.editor)) {
|
if (!isEngine(this.editor)) {
|
||||||
if (status === 'done') {
|
if (status === 'done') {
|
||||||
|
@ -418,6 +606,7 @@ class VideoComponent extends Card<VideoValue> {
|
||||||
: value.download,
|
: value.download,
|
||||||
};
|
};
|
||||||
this.container = $(this.renderTemplate(newValue));
|
this.container = $(this.renderTemplate(newValue));
|
||||||
|
this.video = this.container.find('video');
|
||||||
center.empty();
|
center.empty();
|
||||||
center.append(this.container);
|
center.append(this.container);
|
||||||
this.initPlayer();
|
this.initPlayer();
|
||||||
|
@ -436,17 +625,27 @@ class VideoComponent extends Card<VideoValue> {
|
||||||
);
|
);
|
||||||
return this.container;
|
return this.container;
|
||||||
} else {
|
} else {
|
||||||
return $(this.renderTemplate(value));
|
this.container = $(this.renderTemplate(value));
|
||||||
|
return this.container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleClick = () => {
|
||||||
|
if (isEngine(this.editor) && !this.activated) {
|
||||||
|
this.editor.card.activate(this.root);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
didRender() {
|
didRender() {
|
||||||
super.didRender();
|
super.didRender();
|
||||||
this.container?.on(isMobile ? 'touchstart' : 'click', () => {
|
this.toolbarModel?.setDefaultAlign('top');
|
||||||
if (isEngine(this.editor) && !this.activated) {
|
this.container?.on('click', this.handleClick);
|
||||||
this.editor.card.activate(this.root);
|
}
|
||||||
}
|
|
||||||
});
|
destroy() {
|
||||||
|
super.destroy();
|
||||||
|
this.container?.off('click', this.handleClick);
|
||||||
|
window.removeEventListener('resize', this.onWindowResize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
NodeInterface,
|
NodeInterface,
|
||||||
Plugin,
|
Plugin,
|
||||||
PluginEntry,
|
PluginEntry,
|
||||||
|
PluginOptions,
|
||||||
READY_CARD_KEY,
|
READY_CARD_KEY,
|
||||||
sanitizeUrl,
|
sanitizeUrl,
|
||||||
SchemaInterface,
|
SchemaInterface,
|
||||||
|
@ -18,12 +19,15 @@ import VideoComponent, { VideoValue } from './component';
|
||||||
import VideoUploader from './uploader';
|
import VideoUploader from './uploader';
|
||||||
import locales from './locales';
|
import locales from './locales';
|
||||||
|
|
||||||
export default class VideoPlugin extends Plugin<{
|
export interface VideoOptions extends PluginOptions {
|
||||||
onBeforeRender?: (
|
onBeforeRender?: (
|
||||||
action: 'download' | 'query' | 'cover',
|
action: 'download' | 'query' | 'cover',
|
||||||
url: string,
|
url: string,
|
||||||
) => string;
|
) => string;
|
||||||
}> {
|
showTitle?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class VideoPlugin extends Plugin<VideoOptions> {
|
||||||
static get pluginName() {
|
static get pluginName() {
|
||||||
return 'video';
|
return 'video';
|
||||||
}
|
}
|
||||||
|
@ -46,6 +50,10 @@ export default class VideoPlugin extends Plugin<{
|
||||||
cover?: string,
|
cover?: string,
|
||||||
size?: number,
|
size?: number,
|
||||||
download?: string,
|
download?: string,
|
||||||
|
naturalWidth?: number,
|
||||||
|
naturalHeight?: number,
|
||||||
|
width?: number,
|
||||||
|
height?: number,
|
||||||
): void {
|
): void {
|
||||||
const value: VideoValue = {
|
const value: VideoValue = {
|
||||||
status,
|
status,
|
||||||
|
@ -55,6 +63,10 @@ export default class VideoPlugin extends Plugin<{
|
||||||
name: name || url,
|
name: name || url,
|
||||||
size,
|
size,
|
||||||
download,
|
download,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
naturalWidth,
|
||||||
|
naturalHeight,
|
||||||
};
|
};
|
||||||
if (status === 'error') {
|
if (status === 'error') {
|
||||||
value.url = '';
|
value.url = '';
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
|
|
||||||
import VideoComponent from './component';
|
import VideoComponent from './component';
|
||||||
|
|
||||||
export interface Options extends PluginOptions {
|
export interface VideoUploaderOptions extends PluginOptions {
|
||||||
/**
|
/**
|
||||||
* 视频上传地址
|
* 视频上传地址
|
||||||
*/
|
*/
|
||||||
|
@ -70,6 +70,9 @@ export interface Options extends PluginOptions {
|
||||||
id?: string;
|
id?: string;
|
||||||
cover?: string;
|
cover?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
|
name?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
}
|
}
|
||||||
| string;
|
| string;
|
||||||
};
|
};
|
||||||
|
@ -96,7 +99,7 @@ export interface Options extends PluginOptions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class extends Plugin<Options> {
|
export default class extends Plugin<VideoUploaderOptions> {
|
||||||
private cardComponents: { [key: string]: VideoComponent } = {};
|
private cardComponents: { [key: string]: VideoComponent } = {};
|
||||||
|
|
||||||
static get pluginName() {
|
static get pluginName() {
|
||||||
|
@ -219,6 +222,12 @@ export default class extends Plugin<Options> {
|
||||||
const download: string =
|
const download: string =
|
||||||
response.download ||
|
response.download ||
|
||||||
(response.data && response.data.download);
|
(response.data && response.data.download);
|
||||||
|
const width: number =
|
||||||
|
response.width ||
|
||||||
|
(response.data && response.data.width);
|
||||||
|
const height: number =
|
||||||
|
response.height ||
|
||||||
|
(response.data && response.data.height);
|
||||||
let status: string =
|
let status: string =
|
||||||
response.status ||
|
response.status ||
|
||||||
(response.data && response.data.status);
|
(response.data && response.data.status);
|
||||||
|
@ -232,6 +241,8 @@ export default class extends Plugin<Options> {
|
||||||
cover?: string;
|
cover?: string;
|
||||||
download?: string;
|
download?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
}
|
}
|
||||||
| string;
|
| string;
|
||||||
} = {
|
} = {
|
||||||
|
@ -242,6 +253,8 @@ export default class extends Plugin<Options> {
|
||||||
cover,
|
cover,
|
||||||
download,
|
download,
|
||||||
status,
|
status,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (parse) {
|
if (parse) {
|
||||||
|
@ -253,6 +266,9 @@ export default class extends Plugin<Options> {
|
||||||
cover?: string;
|
cover?: string;
|
||||||
download?: string;
|
download?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
|
name?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
};
|
};
|
||||||
if (typeof customizeResult.data === 'string')
|
if (typeof customizeResult.data === 'string')
|
||||||
result.data = {
|
result.data = {
|
||||||
|
@ -307,6 +323,8 @@ export default class extends Plugin<Options> {
|
||||||
? { url: result.data }
|
? { url: result.data }
|
||||||
: {
|
: {
|
||||||
...result.data,
|
...result.data,
|
||||||
|
naturalWidth: result.data.width,
|
||||||
|
naturalHeight: result.data.height,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const sendToWormhole = require('stream-wormhole');
|
const sendToWormhole = require('stream-wormhole');
|
||||||
|
const ffmpeg = require('fluent-ffmpeg');
|
||||||
const { Controller } = require('egg');
|
const { Controller } = require('egg');
|
||||||
|
|
||||||
class UploadController extends Controller {
|
class UploadController extends Controller {
|
||||||
|
@ -159,10 +160,53 @@ class UploadController extends Controller {
|
||||||
// 监听写入完成事件
|
// 监听写入完成事件
|
||||||
remoteFileStrem.on('finish', () => {
|
remoteFileStrem.on('finish', () => {
|
||||||
if (errFlag) return;
|
if (errFlag) return;
|
||||||
resolve({
|
const result = {
|
||||||
url,
|
url,
|
||||||
download: url,
|
download: url,
|
||||||
});
|
name: sourceName,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
ffmpeg.setFfmpegPath(
|
||||||
|
path.join(app.baseDir, './app/ffmpeg/win/ffmpeg.exe'),
|
||||||
|
);
|
||||||
|
ffmpeg.setFfprobePath(
|
||||||
|
path.join(app.baseDir, './app/ffmpeg/win/ffprobe.exe'),
|
||||||
|
);
|
||||||
|
ffmpeg.ffprobe(filePath, (err, metadata) => {
|
||||||
|
const fileName = new Date().getTime() + '-v-image.png'; // stream对象也包含了文件名,大小等基本信息
|
||||||
|
|
||||||
|
// 创建文件写入路径
|
||||||
|
const imagePath = path.join(
|
||||||
|
app.baseDir,
|
||||||
|
`/app/public/upload/${fileName}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const { width, height } = metadata.streams[0];
|
||||||
|
result.width = width;
|
||||||
|
result.height = height;
|
||||||
|
ffmpeg(filePath)
|
||||||
|
.screenshots({
|
||||||
|
timestamps: ['50%'],
|
||||||
|
filename: fileName,
|
||||||
|
folder: path.join(
|
||||||
|
app.baseDir,
|
||||||
|
'/app/public/upload',
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.on('end', () => {
|
||||||
|
result.cover = `${this.domain}/upload/${fileName}`;
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -52,15 +52,28 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "yreo1zOnA0tpLMpO4h",
|
"id": "B2apyT5NIgXPe4tRX7",
|
||||||
"title": "g12s",
|
"title": "hj",
|
||||||
"status": "true",
|
"status": "false",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"username": "test",
|
"username": "Guest-undefined",
|
||||||
|
"content": "ghjhj",
|
||||||
|
"createdAt": 1639325623775
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kAoP518hzaPYD9Mx9z",
|
||||||
|
"title": "dsfdf",
|
||||||
|
"status": "true",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"username": "Guest-2",
|
||||||
"content": "sdfdf",
|
"content": "sdfdf",
|
||||||
"createdAt": 1639328040653
|
"createdAt": 1639571978834
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"id": "demo",
|
"id": "demo",
|
||||||
"content": {
|
"content": {
|
||||||
"value": "<p data-id=\"peafab28-UeGZGcV7\"><br /></p><card type=\"block\" name=\"table\" editable=\"true\" value=\"data:%7B%22rows%22%3A3%2C%22cols%22%3A3%2C%22id%22%3A%221A2IV%22%2C%22type%22%3A%22block%22%2C%22height%22%3A102%2C%22width%22%3A690%2C%22html%22%3A%22%3Ctable%20class%3D%5C%22data-table%5C%22%20data-id%3D%5C%22t21b6eb9-LlLf0GOG%5C%22%20style%3D%5C%22width%3A%20690px%3B%5C%22%3E%3Ccolgroup%20data-id%3D%5C%22c9d5c669-CVY52f6H%5C%22%3E%3Ccol%20data-id%3D%5C%22c5da60d0-iFijk1EW%5C%22%20width%3D%5C%22230%5C%22%20span%3D%5C%221%5C%22%20%2F%3E%3Ccol%20data-id%3D%5C%22c5da60d0-0D2HF3SP%5C%22%20width%3D%5C%22230%5C%22%20span%3D%5C%221%5C%22%20%2F%3E%3Ccol%20data-id%3D%5C%22c5da60d0-Ni4KSj8K%5C%22%20width%3D%5C%22230%5C%22%20span%3D%5C%221%5C%22%20%2F%3E%3C%2Fcolgroup%3E%3Ctbody%20data-id%3D%5C%22t61d509e-Tf2WYKhU%5C%22%3E%3Ctr%20data-id%3D%5C%22t8f11d90-T7IL81Xf%5C%22%20style%3D%5C%22height%3A%2033px%3B%5C%22%3E%3Ctd%20data-id%3D%5C%22td18b8d3-2EeOGLQb%5C%22%3E%3Cp%20data-id%3D%5C%22peafab28-9N4g8AhO%5C%22%3Esdfdfkk%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22td18b8d3-MgP6Ob2g%5C%22%3E%3Cp%20data-id%3D%5C%22peafab28-Pl6XNbLN%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22td18b8d3-lP0l6F6H%5C%22%20class%3D%5C%22table-last-row%5C%22%3E%3Cp%20data-id%3D%5C%22peafab28-nV334EmX%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3C%2Ftr%3E%3Ctr%20data-id%3D%5C%22t8f11d90-Ke7XQKBS%5C%22%20style%3D%5C%22height%3A%2033px%3B%5C%22%3E%3Ctd%20data-id%3D%5C%22td18b8d3-p7m1hYhY%5C%22%3E%3Cp%20data-id%3D%5C%22peafab28-PJO7hR6Q%5C%22%3Edfgfdgsdf4kk%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22td18b8d3-hUPPS4P6%5C%22%3E%3Cp%20data-id%3D%5C%22peafab28-30U3kFAE%5C%22%3Edfgfg%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22td18b8d3-8mpQLAJH%5C%22%20class%3D%5C%22table-last-row%5C%22%3E%3Cp%20data-id%3D%5C%22peafab28-c6mQN6Hb%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3C%2Ftr%3E%3Ctr%20data-id%3D%5C%22t8f11d90-oNKFnQHT%5C%22%20style%3D%5C%22height%3A%2033px%3B%5C%22%3E%3Ctd%20data-id%3D%5C%22td18b8d3-l78d0KUc%5C%22%20class%3D%5C%22table-last-column%5C%22%3E%3Cp%20data-id%3D%5C%22peafab28-V4NEMkLV%5C%22%3Edfggsdfb1kk%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22td18b8d3-hLULe0Ql%5C%22%20class%3D%5C%22table-last-column%5C%22%3E%3Cp%20data-id%3D%5C%22peafab28-8e3tXqmC%5C%22%3Edfgfg%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22td18b8d3-9cNNI6bi%5C%22%20class%3D%5C%22table-last-column%20table-last-row%5C%22%3E%3Cp%20data-id%3D%5C%22peafab28-YApPlQd6%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3C%2Ftr%3E%3C%2Ftbody%3E%3C%2Ftable%3E%22%7D\"></card><p data-id=\"peafab28-8nHRgJnn\">sdfabcdefg123dsf1111d1g12s</p><p data-id=\"peafab28-9QE8Ib78\"><strong>12ab12c21123</strong>12345</p><card type=\"block\" name=\"codeblock\" editable=\"false\" value=\"data:%7B%22id%22%3A%222Cwmg%22%2C%22type%22%3A%22block%22%2C%22mode%22%3A%22plain%22%2C%22code%22%3A%22hhhhjhjhjkkk%22%7D\"></card><p data-id=\"peafab28-3d0SPNQb\">1243sffffd</p>",
|
"value": "<h1 data-id=\"hbc788f1-HCEiiZG4\" id=\"hbc788f1-HCEiiZG4\">sdfsdfsdf</h1><p data-id=\"pd157317-9mX1S9ff\">sdfdsfdsfdfdfg</p><card type=\"block\" name=\"table\" editable=\"true\" value=\"data:%7B%22rows%22%3A3%2C%22cols%22%3A5%2C%22overflow%22%3A%7B%7D%2C%22id%22%3A%22z3W7h%22%2C%22type%22%3A%22block%22%2C%22height%22%3A105%2C%22width%22%3A1295%2C%22html%22%3A%22%3Ctable%20class%3D%5C%22data-table%5C%22%20data-id%3D%5C%22t7216feb-UR4lJETQ%5C%22%20style%3D%5C%22width%3A%201295px%3B%5C%22%3E%3Ccolgroup%20data-id%3D%5C%22c82d01ad-cO2cMESN%5C%22%3E%3Ccol%20data-id%3D%5C%22cac3d390-BEQW2Af8%5C%22%20width%3D%5C%22259%5C%22%20span%3D%5C%221%5C%22%20%2F%3E%3Ccol%20data-id%3D%5C%22cac3d390-BEQW2Af8%5C%22%20width%3D%5C%22259%5C%22%20span%3D%5C%221%5C%22%20%2F%3E%3Ccol%20data-id%3D%5C%22cac3d390-5aAaY4PI%5C%22%20width%3D%5C%22259%5C%22%20span%3D%5C%221%5C%22%20%2F%3E%3Ccol%20data-id%3D%5C%22cac3d390-5aAaY4PI%5C%22%20width%3D%5C%22259%5C%22%20span%3D%5C%221%5C%22%20%2F%3E%3Ccol%20data-id%3D%5C%22cac3d390-5aAaY4PI%5C%22%20width%3D%5C%22259%5C%22%20span%3D%5C%221%5C%22%20%2F%3E%3C%2Fcolgroup%3E%3Ctbody%20data-id%3D%5C%22tc1e2dd5-ca338IhV%5C%22%3E%3Ctr%20data-id%3D%5C%22t40b42a1-aYJWmUJL%5C%22%20style%3D%5C%22height%3A%2035px%3B%5C%22%3E%3Ctd%20data-id%3D%5C%22t5815cab-bCGT82Ui%5C%22%3E%3Cp%20data-id%3D%5C%22pd157317-J6C5nVfC%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22t5815cab-faIHF5bL%5C%22%3E%3Cp%20data-id%3D%5C%22pd157317-iKc2C558%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22t5815cab-ADmVOlBb%5C%22%3E%3Cp%20data-id%3D%5C%22pd157317-B4BVQiFI%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%3E%3Cp%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22t5815cab-Gm3I1IW3%5C%22%20class%3D%5C%22table-last-row%5C%22%3E%3Cp%20data-id%3D%5C%22pd157317-A0494K62%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3C%2Ftr%3E%3Ctr%20data-id%3D%5C%22t40b42a1-WRTdh09h%5C%22%20style%3D%5C%22height%3A%2035px%3B%5C%22%3E%3Ctd%20data-id%3D%5C%22t5815cab-kTR4C3DT%5C%22%3E%3Cp%20data-id%3D%5C%22pd157317-5fDBMYOP%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22t5815cab-rIYUHo3C%5C%22%3E%3Cp%20data-id%3D%5C%22pd157317-NJUMh6el%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22t5815cab-h9961ZOB%5C%22%3E%3Cp%20data-id%3D%5C%22pd157317-Wa0VLLCM%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%3E%3Cp%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22t5815cab-l7Cj19AZ%5C%22%20class%3D%5C%22table-last-row%5C%22%3E%3Cp%20data-id%3D%5C%22pd157317-WAHoEoYg%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3C%2Ftr%3E%3Ctr%20data-id%3D%5C%22t40b42a1-GWMmlanJ%5C%22%20style%3D%5C%22height%3A%2035px%3B%5C%22%3E%3Ctd%20data-id%3D%5C%22t5815cab-kjgohUZP%5C%22%20class%3D%5C%22table-last-column%5C%22%3E%3Cp%20data-id%3D%5C%22pd157317-TJXbZWjs%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20class%3D%5C%22table-last-column%5C%22%20data-id%3D%5C%22te8113ae-FNQhbbZA%5C%22%3E%3Cp%20data-id%3D%5C%22pd157317-GT5JJPm6%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20class%3D%5C%22table-last-column%5C%22%20data-id%3D%5C%22te8113ae-iThMaAVQ%5C%22%3E%3Cp%20data-id%3D%5C%22pd157317-YnO3LRJI%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20class%3D%5C%22table-last-column%5C%22%3E%3Cp%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3Ctd%20data-id%3D%5C%22t5815cab-57DBKodH%5C%22%20class%3D%5C%22table-last-column%20table-last-row%5C%22%3E%3Cp%20data-id%3D%5C%22pd157317-Y7so1iqZ%5C%22%3E%3Cbr%20%2F%3E%3C%2Fp%3E%3C%2Ftd%3E%3C%2Ftr%3E%3C%2Ftbody%3E%3C%2Ftable%3E%22%7D\"></card><p data-id=\"pd157317-kOu29md8\">fgfdgfdg</p>",
|
||||||
"paths": [
|
"paths": [
|
||||||
{
|
{
|
||||||
"id": ["yreo1zOnA0tpLMpO4h"],
|
"id": ["kAoP518hzaPYD9Mx9z"],
|
||||||
"path": [
|
"path": [
|
||||||
[2, 0, 22],
|
[1, 0, 6],
|
||||||
[2, 0, 26]
|
[1, 0, 11]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
"egg-scripts": "^2.13.0",
|
"egg-scripts": "^2.13.0",
|
||||||
"egg-view-assets": "^1.7.0",
|
"egg-view-assets": "^1.7.0",
|
||||||
"egg-view-nunjucks": "^2.3.0",
|
"egg-view-nunjucks": "^2.3.0",
|
||||||
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"jsdom": "^16.4.0",
|
"jsdom": "^16.4.0",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"qs": "^6.7.0",
|
"qs": "^6.7.0",
|
||||||
|
|
Loading…
Reference in New Issue