修复自定义指令lemon-contextmenu报错的问题

This commit is contained in:
范君
2021-02-01 15:16:37 +08:00
parent 134353495f
commit 740d9a527d
36 changed files with 16518 additions and 691 deletions
-1
View File
@@ -1,6 +1,5 @@
.DS_Store .DS_Store
node_modules node_modules
/dist
# local env files # local env files
.env.local .env.local
+9 -9
View File
@@ -1,14 +1,12 @@
# Lemon IMUI # Lemon IMUI
[中文文档](docs/APIs_zh.md).
基于 VUE 2.0 的 IM 聊天组件 基于 VUE 2.0 的 IM 聊天组件
#### 特性 #### 特性
- 拥有丰富的自定义功能,任意搭配出不同风格的聊天界面 - 拥有丰富的自定义功能,任意搭配出不同风格的聊天界面
- 可以单独使用内部组件,比如编辑框/按钮/popover 等 - 不依赖任何第三方 UI 组件库
- 不依赖任何第三方组件库 - 可任意扩展聊天消息类型
- 可任意扩展的聊天消息类型
#### 安装 #### 安装
@@ -17,15 +15,17 @@
#### 使用 #### 使用
```javascript ```javascript
import LemonIMUI from 'lemon-imui' import LemonIMUI from 'lemon-imui';
import "lemon-imui/dist/index.css"; import 'lemon-imui/dist/index.css';
Vue.use(LemonIMUI) Vue.use(LemonIMUI);
``` ```
```html ```html
<lemon-imui ref="IMUI" /> <lemon-imui ref="IMUI" />
``` ```
#### 示例 #### 示例 · 文档
[lemon-imui-examples](http://june000.gitee.io/lemon-im). [lemon-imui](http://june000.gitee.io/lemon-im).
[QQ 交流群:1081773406](https://qm.qq.com/cgi-bin/qm/qr?k=MzwO4MT20zYQEXP8gq-GbjSJFA0qK15_&jump_from=webapi).
+10
View File
@@ -0,0 +1,10 @@
<meta charset="utf-8">
<title>index demo</title>
<script src="./index.umd.js"></script>
<link rel="stylesheet" href="./index.css">
<script>
console.log(index)
</script>
+7012
View File
File diff suppressed because it is too large Load Diff
+1
View File
File diff suppressed because one or more lines are too long
+7022
View File
File diff suppressed because it is too large Load Diff
+1
View File
File diff suppressed because one or more lines are too long
-232
View File
@@ -1,232 +0,0 @@
# Lemon-IMUI
### Contents
- contact
```javascript
{
//用户唯一ID
id: "",
//昵称
displayName: "工作协作群",
//头像URL
avatar: "http://upload.qqbodys.com/img/weixin/20170804/ji5qxg1am5ztm.jpg",
//会话类型 single | many
type: "single",
//通讯录索引,默认根据字母排序,也可以手动排序“[1]最近联系人”
index: "A",
//未读消息
unread: 0,
//最近消息时间
lastSendTime: 1566047865417,
//最近消息内容
lastContent: "2"
}
```
- message
```javascript
{
//消息唯一ID
id: "",
status: "succeed",
//消息类型 voice | file | video | image | text
type: "text",
//消息发送时间
sendTime: 1572415923000,
//消息内容 | URL
content: generateRandWord(),
//文件大小
fileSize: 1231,
//文件名称
fileName: "asdasd.doc",
//当前会话ID
toContactId:"",
//发送消息的用户
fromUser:{
id: "system",
displayName: "系统测试",
avatar: "http://upload.qqbodys.com/allimg/1710/1035512943-0.jpg"
};
}
```
- menu
```javascript
{
//导航名称, 保留字段 lastMessages 和 contacts
name: "custom1",
//鼠标停留时显示文字
title: "自定义按钮1",
//未读角标
unread: 0,
//外观
render: menu => {
return <i class="lemon-icon-attah" />;
},
//打开内容
renderContainer: () => {
return <div>自定义</div>;
},
//强制显示在底部
isBottom: true
}
```
### Props
- user
```javascript
{
id:'',
avatar:'',
displayName:'',
}
```
个人信息
- currentContactId
当前会话联系人 ID
- currentContact
当前会话联系人信息
- messageTimeFormat
消息列表时间格式化函数
- contactTimeFormat
联系人时间格式化规则
- hideDrawer
是否隐藏抽屉
- hideMenuAvatar
是否隐藏导航头像
- hideMenuAvatar
是否隐藏导航
### Methods
- initMenus([menu]);
初始化导航
- initContacts([contact]);
初始化联系人
- initEmoji()
初始化表情
```javascript
IMUI.initEmoji([
{
label: '表情',
children: [
{
name: '1f600',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f600.png'
},
{
name: '1f62c',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f62c.png'
}
]
},
{
label: '收藏',
children: [
{
name: '1f62c',
title: '微笑',
src: 'https://twemoji.maxcdn.com/2/72x72/1f62c.png'
}
]
}
])
```
- appendMessage(message)
在当前聊天窗口插入新消息
- removeMessage(messageId, contactId)
删除聊天消息
- updateMessage(messageId, contactId, message)
修改聊天聊天消息
- updateContact(contactId,contact)
修改联系人
- getMessages(contactId)
返回所有本地消息,传入 contactId 只返回与该联系人的消息
- getContacts()
获取所有联系人
- openDrawer(vnode)
打开抽屉
- closeDrawer()
关闭抽屉
- changeDrawer(vnode)
切换抽屉显示
- changeMenu(menuName)
切换导航
- changeContact(contactId)
切换聊天对象
- messageViewToBottom()
将当前聊天窗口滚动到底部
- setLastContentRender(messageType, render)
配置联系人列表最新消息的渲染函数
```javascript
IMUI.setLastContentRender('image', message => {
return <span>[最新图片]</span>
})
```
- lastContentRender(message)
根据 message 渲染联系人列表最新消息 DOM
```javascript
IMUI.updateContact(contact.id, {
lastContent: IMUI.lastContentRender(message)
})
```
### Scoped Slot
- cover
自定义聊天封面
- contact-title 参数{ contact }
自定义联系人标题
- message-sidebar
插入到最新消息列顶部
- contact-sidebar
插入到联系人列顶部
- contact-info 参数{ contact }
自定义联系人信息
### Events
- change-menu(menuName)
切换导航
- change-contact(contact)
切换导航会话
- pull-messages(contact,next)
拉取新消息
- next([message],isEnd) [isEnd 是否无更多数据]
- message-click(event, key, message)
- event 事件
- key 触发目标
- message 消息内容
- menu-avatar-click()
点击导航头像
- send(message, next, file)
- message 当前消息体
- next(message) 调用该函数完成消息发送
- file 上传的文件
+1482 -101
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>Lemon IMUI</title><link href=css/index.08b1f4f3.css rel=preload as=style><link href=js/chunk-vendors.e4810482.js rel=preload as=script><link href=js/index.20b5dfe7.js rel=preload as=script><link href=css/index.08b1f4f3.css rel=stylesheet></head><body><noscript><strong>We're sorry but flat-im doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=js/chunk-vendors.e4810482.js></script><script src=js/index.20b5dfe7.js></script></body></html> <!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>Lemon IMUI</title><link href=css/index.e91d4c69.css rel=preload as=style><link href=js/chunk-vendors.2abee366.js rel=preload as=script><link href=js/index.e80c8c21.js rel=preload as=script><link href=css/index.e91d4c69.css rel=stylesheet></head><body><noscript><strong>We're sorry but flat-im doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=js/chunk-vendors.2abee366.js></script><script src=js/index.e80c8c21.js></script></body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+30
View File
@@ -0,0 +1,30 @@
<script>
export default {
name: "lemonMessageVoice",
inheritAttrs: false,
inject: ["IMUI"],
render() {
return (
<lemon-message-basic
class="lemon-message-voice"
props={{ ...this.$attrs }}
scopedSlots={{
content: props => {
return <span>{props.content}&nbsp;🔈</span>;
}
}}
/>
);
}
};
</script>
<style lang="stylus">
.lemon-message.lemon-message-voice
user-select none
.lemon-message__content
border 2px solid #000
font-size 12px
cursor pointer
&::before
display none
</style>
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "lemon-imui", "name": "lemon-imui",
"version": "1.0.4", "version": "1.6.5",
"main": "dist/index.umd.min.js", "main": "dist/index.umd.min.js",
"description": "基于 VUE2.0 的 IM 聊天组件", "description": "基于 VUE2.0 的 IM 聊天组件",
"homepage": "https://github.com/fanjyy/lemon-imui", "homepage": "https://github.com/fanjyy/lemon-imui",
+10 -1
View File
@@ -1,12 +1,19 @@
<script> <script>
export default { export default {
name: "LemonAvatar", name: "LemonAvatar",
inject: ["IMUI"],
props: { props: {
src: String, src: String,
icon: { icon: {
type: String, type: String,
default: "lemon-icon-people" default: "lemon-icon-people"
}, },
circle: {
type: Boolean,
default() {
return this.IMUI ? this.IMUI.avatarCricle : false;
}
},
size: { size: {
type: Number, type: Number,
default: 32 default: 32
@@ -21,7 +28,7 @@ export default {
return ( return (
<span <span
style={this.style} style={this.style}
class="lemon-avatar" class={["lemon-avatar", { "lemon-avatar--circle": this.circle }]}
on-click={e => this.$emit("click", e)} on-click={e => this.$emit("click", e)}
> >
{this.imageFinishLoad && <i class={this.icon} />} {this.imageFinishLoad && <i class={this.icon} />}
@@ -65,6 +72,8 @@ export default {
overflow hidden overflow hidden
vertical-align middle vertical-align middle
border-radius 4px border-radius 4px
+m(circle)
border-radius 50%
img img
width 100% width 100%
height 100% height 100%
+21 -10
View File
@@ -2,12 +2,16 @@
export default { export default {
name: "LemonButton", name: "LemonButton",
props: { props: {
color: {
type: String,
default: "default"
},
disabled: Boolean disabled: Boolean
}, },
render() { render() {
return ( return (
<button <button
class="lemon-button" class={["lemon-button", `lemon-button--color-${this.color}`]}
disabled={this.disabled} disabled={this.disabled}
type="button" type="button"
on-click={this._handleClick} on-click={this._handleClick}
@@ -47,13 +51,20 @@ export default {
background-color #fff background-color #fff
box-shadow 0 2px 0 rgba(0, 0, 0, 0.015) box-shadow 0 2px 0 rgba(0, 0, 0, 0.015)
text-shadow 0 -1px 0 rgba(0, 0, 0, 0.12) text-shadow 0 -1px 0 rgba(0, 0, 0, 0.12)
&:hover:not([disabled]) +m(color-default)
border-color #666 &:hover:not([disabled])
color #333 border-color #666
&:active color #333
background-color #ddd &:active
&[disabled] background-color #ddd
cursor not-allowed &[disabled]
color #aaa cursor not-allowed
background #eee color #aaa
background #eee
+m(color-grey)
background #e1e1e1
border-color #e1e1e1
color #666
&:hover:not([disabled])
border-color #bbb
</style> </style>
+36 -26
View File
@@ -1,9 +1,17 @@
<script> <script>
import { isString, isToday } from "utils/validate"; import { isString, isToday } from "utils/validate";
import { timeFormat } from "utils"; import { timeFormat, useScopedSlot } from "utils";
export default { export default {
name: "LemonContact", name: "LemonContact",
components: {}, components: {},
inject: {
IMUI: {
from: "IMUI",
default() {
return this;
}
}
},
data() { data() {
return {}; return {};
}, },
@@ -18,23 +26,33 @@ export default {
} }
}, },
render() { render() {
const { contact } = this;
return ( return (
<div <div
class={["lemon-contact", { "lemon-contact--name-center": this.simple }]} class={["lemon-contact", { "lemon-contact--name-center": this.simple }]}
on-click={e => this._handleClick(e, contact)} on-click={e => this._handleClick(e, this.contact)}
> >
{useScopedSlot(
this.$scopedSlots.default,
this._renderInner(),
this.contact
)}
</div>
);
},
created() {},
mounted() {},
computed: {},
watch: {},
methods: {
_renderInner() {
const { contact } = this;
return [
<lemon-badge <lemon-badge
count={!this.simple ? contact.unread : 0} count={!this.simple ? contact.unread : 0}
class="lemon-contact__avatar" class="lemon-contact__avatar"
native-on-click={e => this._handleBubbleClick(e, contact)}
> >
<lemon-avatar <lemon-avatar size={40} src={contact.avatar} />
size={40} </lemon-badge>,
native-on-click={e => this._handleAvatarClick(e, contact)}
src={contact.avatar}
/>
</lemon-badge>
<div class="lemon-contact__inner"> <div class="lemon-contact__inner">
<p class="lemon-contact__label"> <p class="lemon-contact__label">
<span class="lemon-contact__name">{contact.displayName}</span> <span class="lemon-contact__name">{contact.displayName}</span>
@@ -54,24 +72,10 @@ export default {
</p> </p>
)} )}
</div> </div>
</div> ];
); },
},
created() {},
mounted() {},
computed: {},
watch: {},
methods: {
_handleClick(e, data) { _handleClick(e, data) {
this.$emit("click", data); this.$emit("click", data);
},
_handleAvatarClick(e, data) {
e.stopPropagation();
this.$emit("avatar-click", data);
},
_handleBubbleClick(e, data) {
e.stopPropagation();
this.$emit("bubble-click", data);
} }
} }
}; };
@@ -85,6 +89,7 @@ export default {
box-sizing border-box box-sizing border-box
overflow hidden overflow hidden
background #efefef background #efefef
text-align left
p p
margin 0 margin 0
+m(active) +m(active)
@@ -121,12 +126,17 @@ export default {
+e(content) +e(content)
font-size 12px font-size 12px
color #999 color #999
height 18px
line-height 18px
margin-top 1px !important
ellipsis() ellipsis()
img img
height 14px height 14px
display inline-block display inline-block
vertical-align middle vertical-align middle
margin 0 1px margin 0 1px
position relative
top -1px
+m(name-center) +m(name-center)
+e(label) +e(label)
padding-bottom 0 padding-bottom 0
+252 -56
View File
@@ -1,31 +1,118 @@
<script> <script>
import { toEmojiName } from "utils"; import { toEmojiName, useScopedSlot, clearHtmlExcludeImg } from "utils";
const exec = (val, command = "insertHTML") => { const exec = (val, command = "insertHTML") => {
document.execCommand(command, false, val); document.execCommand(command, false, val);
}; };
const selection = window.getSelection(); const selection = window.getSelection();
let lastSelectionRange; let lastSelectionRange;
let emojiData = []; let emojiData = [];
let isInitTool = false;
export default { export default {
name: "LemonEditor", name: "LemonEditor",
inject: {
IMUI: {
from: "IMUI",
default() {
return this;
}
}
},
components: {}, components: {},
props: {}, props: {
tools: {
type: Array,
default: () => []
},
sendText: {
type: String,
default: "发 送"
},
sendKey: {
type: Function,
default(e) {
return e.keyCode == 13 && e.ctrlKey === true;
}
}
},
data() { data() {
this.clipboardBlob = null;
return { return {
//URL
clipboardUrl: "",
submitDisabled: true, submitDisabled: true,
proxyTools: [],
accept: "" accept: ""
}; };
}, },
created() {}, created() {
mounted() { if (this.tools && this.tools.length > 0) {
//this.$refs.fileInput.addEventListener("change", this._handleChangeFile); this.initTools(this.tools);
} else {
this.initTools([
{ name: "emoji" },
{ name: "uploadFile" },
{ name: "uploadImage" }
]);
}
this.IMUI.$on("change-contact", () => {
this.closeClipboardImage();
});
}, },
computed: {},
watch: {},
render() { render() {
//<a-popover trigger="click" overlay-class-name="lemon-editor__emoji"> const toolLeft = [];
const toolRight = [];
this.proxyTools.forEach(({ name, title, render, click, isRight }) => {
click = click || new Function();
const classes = [
"lemon-editor__tool-item",
{ "lemon-editor__tool-item--right": isRight }
];
let node;
if (name == "emoji") {
node =
emojiData.length == 0 ? (
""
) : (
<lemon-popover class="lemon-editor__emoji">
<template slot="content">{this._renderEmojiTabs()}</template>
<div class={classes} title={title}>
{render()}
</div>
</lemon-popover>
);
} else {
node = (
<div class={classes} on-click={click} title={title}>
{render()}
</div>
);
}
if (isRight) {
toolRight.push(node);
} else {
toolLeft.push(node);
}
});
return ( return (
<div class="lemon-editor"> <div class="lemon-editor">
{this.clipboardUrl && (
<div class="lemon-editor__clipboard-image">
<img src={this.clipboardUrl} />
<div>
<lemon-button
style={{ marginRight: "10px" }}
on-click={this.closeClipboardImage}
color="grey"
>
取消
</lemon-button>
<lemon-button on-click={this.sendClipboardImage}>
发送图片
</lemon-button>
</div>
</div>
)}
<input <input
style="display:none" style="display:none"
type="file" type="file"
@@ -35,26 +122,8 @@ export default {
onChange={this._handleChangeFile} onChange={this._handleChangeFile}
/> />
<div class="lemon-editor__tool"> <div class="lemon-editor__tool">
{emojiData.length > 0 && ( <div class="lemon-editor__tool-left">{toolLeft}</div>
<lemon-popover class="lemon-editor__emoji"> <div class="lemon-editor__tool-right">{toolRight}</div>
<template slot="content">{this._renderEmojiTabs()}</template>
<div class="lemon-editor__tool-item">
<i class="lemon-icon-emoji" />
</div>
</lemon-popover>
)}
<div
class="lemon-editor__tool-item"
on-click={() => this._handleSelectFile("*")}
>
<i class="lemon-icon-folder" />
</div>
<div
class="lemon-editor__tool-item"
on-click={() => this._handleSelectFile("image/*")}
>
<i class="lemon-icon-image" />
</div>
</div> </div>
<div class="lemon-editor__inner"> <div class="lemon-editor__inner">
<div <div
@@ -65,18 +134,22 @@ export default {
on-keydown={this._handleKeydown} on-keydown={this._handleKeydown}
on-paste={this._handlePaste} on-paste={this._handlePaste}
on-click={this._handleClick} on-click={this._handleClick}
on-input={this._handleInput}
spellcheck="false" spellcheck="false"
/> />
</div> </div>
<div class="lemon-editor__footer"> <div class="lemon-editor__footer">
<div class="lemon-editor__tip">使用 ctrl + enter 快捷发送消息</div> <div class="lemon-editor__tip">
{useScopedSlot(
this.IMUI.$scopedSlots["editor-footer"],
"使用 ctrl + enter 快捷发送消息"
)}
</div>
<div class="lemon-editor__submit"> <div class="lemon-editor__submit">
<lemon-button <lemon-button
disabled={this.submitDisabled} disabled={this.submitDisabled}
on-click={this._handleSend} on-click={this._handleSend}
> >
{this.sendText}
</lemon-button> </lemon-button>
</div> </div>
</div> </div>
@@ -84,6 +157,68 @@ export default {
); );
}, },
methods: { methods: {
closeClipboardImage() {
this.clipboardUrl = "";
this.clipboardBlob = null;
},
sendClipboardImage() {
if (!this.clipboardBlob) return;
this.$emit("upload", this.clipboardBlob);
this.closeClipboardImage();
},
/**
* 初始化工具栏
*/
initTools(data) {
if (!data) return;
const defaultTools = [
{
name: "emoji",
title: "表情",
click: null,
render: menu => {
return <i class="lemon-icon-emoji" />;
}
},
{
name: "uploadFile",
title: "文件上传",
click: () => this.selectFile("*"),
render: menu => {
return <i class="lemon-icon-folder" />;
}
},
{
name: "uploadImage",
title: "图片上传",
click: () => this.selectFile("image/*"),
render: menu => {
return <i class="lemon-icon-image" />;
}
}
];
let tools = [];
if (Array.isArray(data)) {
const indexMap = {
emoji: 0,
uploadFile: 1,
uploadImage: 2
};
const indexKeys = Object.keys(indexMap);
tools = data.map(item => {
if (indexKeys.includes(item.name)) {
return {
...defaultTools[indexMap[item.name]],
...item
};
}
return item;
});
} else {
tools = defaultTools;
}
this.proxyTools = tools;
},
_saveLastRange() { _saveLastRange() {
lastSelectionRange = selection.getRangeAt(0); lastSelectionRange = selection.getRangeAt(0);
}, },
@@ -97,9 +232,6 @@ export default {
_handleClick() { _handleClick() {
this._saveLastRange(); this._saveLastRange();
}, },
_handleInput() {
this._checkSubmitDisabled();
},
_renderEmojiTabs() { _renderEmojiTabs() {
const renderImageGrid = items => { const renderImageGrid = items => {
return items.map(item => ( return items.map(item => (
@@ -131,45 +263,65 @@ export default {
_handleSelectEmoji(item) { _handleSelectEmoji(item) {
this._focusLastRange(); this._focusLastRange();
exec(`<img emoji-name="${item.name}" src="${item.src}"></img>`); exec(`<img emoji-name="${item.name}" src="${item.src}"></img>`);
this._checkSubmitDisabled();
this._saveLastRange(); this._saveLastRange();
}, },
async _handleSelectFile(accept) { async selectFile(accept) {
this.accept = accept; this.accept = accept;
await this.$nextTick(); await this.$nextTick();
this.$refs.fileInput.click(); this.$refs.fileInput.click();
}, },
_handlePaste(e) { _handlePaste(e) {
e.preventDefault(); e.preventDefault();
const { clipboardData } = e; const clipboardData = e.clipboardData || window.clipboardData;
const text = clipboardData.getData("text"); const text = clipboardData.getData("Text");
exec(text, "insertText"); if (text) {
// Array.from(clipboardData.items).forEach(item => { if (window.clipboardData) {
// console.log(item.type); this.$refs.textarea.innerHTML = text;
// }); } else {
//e.target.innerText = text; exec(text, "insertText");
}
} else {
const { blob, blobUrl } = this._getClipboardBlob(clipboardData);
this.clipboardBlob = blob;
this.clipboardUrl = blobUrl;
}
},
_getClipboardBlob(clipboard) {
let blob, blobUrl;
for (var i = 0; i < clipboard.items.length; ++i) {
if (
clipboard.items[i].kind == "file" &&
clipboard.items[i].type.indexOf("image/") !== -1
) {
blob = clipboard.items[i].getAsFile();
blobUrl = (window.URL || window.webkitURL).createObjectURL(blob);
}
}
return { blob, blobUrl };
}, },
_handleKeyup(e) { _handleKeyup(e) {
this._saveLastRange(); this._saveLastRange();
//this._checkSubmitDisabled(); this._checkSubmitDisabled();
}, },
_handleKeydown(e) { _handleKeydown(e) {
const { keyCode } = e; if (this.submitDisabled == false && this.sendKey(e)) {
if (keyCode == 13) { this._handleSend();
// e.preventDefault();
// document.execCommand("defaultParagraphSeparator", false, false);
// exec("<br>");
} }
}, },
getFormatValue() { getFormatValue() {
return toEmojiName( // return toEmojiName(
this.$refs.textarea.innerHTML // this.$refs.textarea.innerHTML
.replace(/<br>|<\/br>/, "") // .replace(/<br>|<\/br>/, "")
.replace(/<div>|<p>/g, "\r\n") // .replace(/<div>|<p>/g, "\r\n")
.replace(/<\/div>|<\/p>/g, "") // .replace(/<\/div>|<\/p>/g, "")
); // );
return this.IMUI.emojiImageToName(this.$refs.textarea.innerHTML);
}, },
_checkSubmitDisabled() { _checkSubmitDisabled() {
this.submitDisabled = !this.$refs.textarea.innerHTML.trim(); this.submitDisabled = !clearHtmlExcludeImg(
this.$refs.textarea.innerHTML.trim()
);
}, },
_handleSend(e) { _handleSend(e) {
const text = this.getFormatValue(); const text = this.getFormatValue();
@@ -190,6 +342,10 @@ export default {
initEmoji(data) { initEmoji(data) {
emojiData = data; emojiData = data;
this.$forceUpdate(); this.$forceUpdate();
},
setValue(val) {
this.$refs.textarea.innerHTML = this.IMUI.emojiNameToImage(val);
this._checkSubmitDisabled();
} }
} }
}; };
@@ -199,28 +355,66 @@ export default {
gap = 10px; gap = 10px;
+b(lemon-editor) +b(lemon-editor)
height 200px height 200px
position relative
flex-column() flex-column()
+e(tool) +e(tool)
display flex display flex
height 40px height 40px
align-items center align-items center
padding-left 5px justify-content space-between
padding 0 5px
+e(tool-left){
display flex
}
+e(tool-right){
display flex
}
+e(tool-item) +e(tool-item)
cursor pointer cursor pointer
padding 4px gap padding 4px gap
height 28px height 28px
line-height 24px;
color #999 color #999
transition all ease .3s transition all ease .3s
font-size 12px
[class^='lemon-icon-'] [class^='lemon-icon-']
line-height 26px line-height 26px
font-size 22px font-size 22px
&:hover &:hover
color #333 color #333
+m(right){
margin-left:auto;
}
+e(inner) +e(inner)
flex 1 flex 1
overflow-x hidden overflow-x hidden
overflow-y auto overflow-y auto
scrollbar-light() scrollbar-light()
+e(clipboard-image)
position absolute
top 0
left 0
width 100%
height 100%
flex-column()
justify-content center
align-items center
background #f4f4f4
z-index 1
img
max-height 66%
max-width 80%
background #e9e9e9
//box-shadow 0 0 20px rgba(0,0,0,0.15)
user-select none
cursor pointer
border-radius 4px
margin-bottom 10px
border 3px dashed #ddd !important
box-sizing border-box
.clipboard-popover-title
font-size 14px
color #333
+e(input) +e(input)
height 100% height 100%
box-sizing border-box box-sizing border-box
@@ -234,6 +428,8 @@ gap = 10px;
height 20px height 20px
padding 0 2px padding 0 2px
pointer-events none pointer-events none
position relative
top -1px
vertical-align middle vertical-align middle
+e(footer) +e(footer)
display flex display flex
+374 -134
View File
@@ -1,6 +1,7 @@
<script> <script>
import { useScopedSlot, fastDone, generateUUID } from "utils"; import { useScopedSlot, funCall, generateUUID, cloneDeep } from "utils";
import { isFunction, isString, isEmpty } from "utils/validate"; import { isFunction, isString, isEmpty } from "utils/validate";
import contextmenu from "../directives/contextmenu";
import { import {
DEFAULT_MENUS, DEFAULT_MENUS,
DEFAULT_MENU_LASTMESSAGES, DEFAULT_MENU_LASTMESSAGES,
@@ -10,11 +11,7 @@ import lastContentRender from "../lastContentRender";
import MemoryCache from "utils/cache/memory"; import MemoryCache from "utils/cache/memory";
const CacheContactContainer = new MemoryCache(); const allMessages = {};
const CacheMenuContainer = new MemoryCache();
const CacheMessageLoaded = new MemoryCache();
const messages = {};
const emojiMap = {}; const emojiMap = {};
let renderDrawerContent = () => {}; let renderDrawerContent = () => {};
@@ -26,6 +23,22 @@ export default {
}; };
}, },
props: { props: {
width: {
type: String,
default: "850px"
},
height: {
type: String,
default: "580px"
},
theme: {
type: String,
default: "default"
},
simple: {
type: Boolean,
default: false
},
/** /**
* 消息时间格式化规则 * 消息时间格式化规则
*/ */
@@ -42,10 +55,23 @@ export default {
default: true default: true
}, },
/** /**
* 初始化时是否隐藏导航按钮上的头像 * 是否隐藏导航按钮上的头像
*/ */
hideMenuAvatar: Boolean, hideMenuAvatar: Boolean,
hideMenu: Boolean, hideMenu: Boolean,
/**
* 是否隐藏消息列表内的联系人名字
*/
hideMessageName: Boolean,
/**
* 是否隐藏消息列表内的发送时间
*/
hideMessageTime: Boolean,
sendKey: Function,
sendText: String,
contextmenu: Array,
contactContextmenu: Array,
avatarCricle: Boolean,
user: { user: {
type: Object, type: Object,
default: () => { default: () => {
@@ -54,12 +80,18 @@ export default {
} }
}, },
data() { data() {
this.CacheContactContainer = new MemoryCache();
this.CacheMenuContainer = new MemoryCache();
this.CacheMessageLoaded = new MemoryCache();
this.CacheDraft = new MemoryCache();
return { return {
drawerVisible: !this.hideDrawer, drawerVisible: !this.hideDrawer,
currentContactId: "", currentContactId: null,
currentMessages: [],
activeSidebar: DEFAULT_MENU_LASTMESSAGES, activeSidebar: DEFAULT_MENU_LASTMESSAGES,
contacts: [], contacts: [],
menus: [] menus: [],
editorTools: []
}; };
}, },
@@ -79,9 +111,6 @@ export default {
await this.$nextTick(); await this.$nextTick();
}, },
computed: { computed: {
currentMessages() {
return messages[this.currentContactId] || [];
},
currentContact() { currentContact() {
return this.contacts.find(item => item.id == this.currentContactId) || {}; return this.contacts.find(item => item.id == this.currentContactId) || {};
}, },
@@ -124,9 +153,34 @@ export default {
...message ...message
}; };
}, },
appendMessage(message, contactId = this.currentContactId) { /**
this._addMessage(message, contactId, 1); * 新增一条消息
this.messageViewToBottom(); */
appendMessage(message, scrollToBottom = false) {
if (allMessages[message.toContactId] === undefined) {
this.updateContact({
id: message.toContactId,
unread: "+1",
lastSendTime: message.sendTime,
lastContent: this.lastContentRender(message)
});
} else {
this._addMessage(message, message.toContactId, 1);
const updateContact = {
id: message.toContactId,
lastContent: this.lastContentRender(message),
lastSendTime: message.sendTime
};
if (message.toContactId == this.currentContactId) {
if (scrollToBottom == true) {
this.messageViewToBottom();
}
this.CacheDraft.remove(message.toContactId);
} else {
updateContact.unread = "+1";
}
this.updateContact(updateContact);
}
}, },
_emitSend(message, next, file) { _emitSend(message, next, file) {
this.$emit( this.$emit(
@@ -134,20 +188,21 @@ export default {
message, message,
(replaceMessage = { status: "succeed" }) => { (replaceMessage = { status: "succeed" }) => {
next(); next();
message = Object.assign(message, replaceMessage); this.updateMessage(Object.assign(message, replaceMessage));
this.forceUpdateMessage(message.id);
}, },
file file
); );
}, },
_handleSend(text) { _handleSend(text) {
const message = this._createMessage({ content: text }); const message = this._createMessage({ content: text });
this.appendMessage(message); this.appendMessage(message, true);
this._emitSend(message, () => { this._emitSend(message, () => {
this.updateContact(message.toContactId, { this.updateContact({
id: message.toContactId,
lastContent: this.lastContentRender(message), lastContent: this.lastContentRender(message),
lastSendTime: message.sendTime lastSendTime: message.sendTime
}); });
this.CacheDraft.remove(message.toContactId);
}); });
}, },
_handleUpload(file) { _handleUpload(file) {
@@ -167,11 +222,12 @@ export default {
}; };
} }
const message = this._createMessage(joinMessage); const message = this._createMessage(joinMessage);
this.appendMessage(message); this.appendMessage(message, true);
this._emitSend( this._emitSend(
message, message,
() => { () => {
this.updateContact(message.toContactId, { this.updateContact({
id: message.toContactId,
lastContent: this.lastContentRender(message), lastContent: this.lastContentRender(message),
lastSendTime: message.sendTime lastSendTime: message.sendTime
}); });
@@ -180,26 +236,36 @@ export default {
); );
}, },
_emitPullMessages(next) { _emitPullMessages(next) {
this._changeContactLock = true;
this.$emit( this.$emit(
"pull-messages", "pull-messages",
this.currentContact, this.currentContact,
(messages, isEnd = false) => { (messages, isEnd = false) => {
this._addMessage(messages, this.currentContactId, 0); this._addMessage(messages, this.currentContactId, 0);
CacheMessageLoaded.set(this.currentContactId, isEnd); this.CacheMessageLoaded.set(this.currentContactId, isEnd);
if (isEnd == true) this.$refs.messages.loaded(); if (isEnd == true) this.$refs.messages.loaded();
this.updateCurrentMessages();
this._changeContactLock = false;
next(isEnd); next(isEnd);
} },
this
); );
}, },
clearCacheContainer(name) { clearCacheContainer(name) {
CacheContactContainer.remove(name); this.CacheContactContainer.remove(name);
CacheMenuContainer.remove(name); this.CacheMenuContainer.remove(name);
}, },
_renderWrapper(children) { _renderWrapper(children) {
return ( return (
<div <div
style={{
width: this.width,
height: this.height
}}
class={[ class={[
"lemon-wrapper", "lemon-wrapper",
`lemon-wrapper--theme-${this.theme}`,
{ "lemon-wrapper--simple": this.simple },
this.drawerVisible && "lemon-wrapper--drawer-show" this.drawerVisible && "lemon-wrapper--drawer-show"
]} ]}
> >
@@ -245,7 +311,7 @@ export default {
{ "lemon-menu__item--active": this.activeSidebar == name } { "lemon-menu__item--active": this.activeSidebar == name }
]} ]}
on-click={() => { on-click={() => {
fastDone(click, () => { funCall(click, () => {
if (name) this.changeMenu(name); if (name) this.changeMenu(name);
}); });
}} }}
@@ -264,51 +330,56 @@ export default {
_renderSidebarMessage() { _renderSidebarMessage() {
return this._renderSidebar( return this._renderSidebar(
[ [
useScopedSlot(this.$scopedSlots["message-sidebar"]), useScopedSlot(this.$scopedSlots["sidebar-message-top"], null, this),
this.lastMessages.map(contact => { this.lastMessages.map(contact => {
return this._renderContact( return this._renderContact(
{ {
contact, contact,
timeFormat: this.contactTimeFormat timeFormat: this.contactTimeFormat
}, },
() => this.changeContact(contact.id) () => this.changeContact(contact.id),
this.$scopedSlots["sidebar-message"]
); );
}) })
], ],
DEFAULT_MENU_LASTMESSAGES DEFAULT_MENU_LASTMESSAGES,
useScopedSlot(this.$scopedSlots["sidebar-message-fixedtop"], null, this)
); );
}, },
_renderContact(props, onClick) { _renderContact(props, onClick, slot) {
const { const {
click: customClick, click: customClick,
renderContainer, renderContainer,
id: contactId id: contactId
} = props.contact; } = props.contact;
const click = () => { const click = () => {
fastDone(customClick, () => { funCall(customClick, () => {
onClick(); onClick();
this._customContainerReady( this._customContainerReady(
renderContainer, renderContainer,
CacheContactContainer, this.CacheContactContainer,
contactId contactId
); );
}); });
}; };
return ( return (
<lemon-contact <lemon-contact
class={{ class={{
"lemon-contact--active": this.currentContactId == props.contact.id "lemon-contact--active": this.currentContactId == props.contact.id
}} }}
v-lemon-contextmenu_contact={this.contactContextmenu}
props={props} props={props}
on-click={click} on-click={click}
/> scopedSlots={{ default: slot }}
></lemon-contact>
); );
}, },
_renderSidebarContact() { _renderSidebarContact() {
let prevIndex; let prevIndex;
return this._renderSidebar( return this._renderSidebar(
[ [
useScopedSlot(this.$scopedSlots["contact-sidebar"]), useScopedSlot(this.$scopedSlots["sidebar-contact-top"], null, this),
this.contacts.map(contact => { this.contacts.map(contact => {
if (!contact.index) return; if (!contact.index) return;
contact.index = contact.index.replace(/\[[0-9]*\]/, ""); contact.index = contact.index.replace(/\[[0-9]*\]/, "");
@@ -321,20 +392,29 @@ export default {
contact: contact, contact: contact,
simple: true simple: true
}, },
() => this.changeContact(contact.id) () => {
this.changeContact(contact.id);
},
this.$scopedSlots["sidebar-contact"]
) )
]; ];
prevIndex = contact.index; prevIndex = contact.index;
return node; return node;
}) })
], ],
DEFAULT_MENU_CONTACTS DEFAULT_MENU_CONTACTS,
useScopedSlot(this.$scopedSlots["sidebar-contact-fixedtop"], null, this)
); );
}, },
_renderSidebar(children, name) { _renderSidebar(children, name, fixedtop) {
return ( return (
<div class="lemon-sidebar" v-show={this.activeSidebar == name}> <div
{children} class="lemon-sidebar"
v-show={this.activeSidebar == name}
on-scroll={this._handleSidebarScroll}
>
<div class="lemon-sidebar__fixed-top">{fixedtop}</div>
<div class="lemon-sidebar__scroll">{children}</div>
</div> </div>
); );
}, },
@@ -356,22 +436,22 @@ export default {
const cls = "lemon-container"; const cls = "lemon-container";
const curact = this.currentContact; const curact = this.currentContact;
let defIsShow = true; let defIsShow = true;
for (const name in CacheContactContainer.get()) { for (const name in this.CacheContactContainer.get()) {
const show = curact.id == name && this.currentIsDefSidebar; const show = curact.id == name && this.currentIsDefSidebar;
defIsShow = !show; defIsShow = !show;
nodes.push( nodes.push(
<div class={cls} v-show={show}> <div class={cls} v-show={show}>
{CacheContactContainer.get(name)} {this.CacheContactContainer.get(name)}
</div> </div>
); );
} }
for (const name in CacheMenuContainer.get()) { for (const name in this.CacheMenuContainer.get()) {
nodes.push( nodes.push(
<div <div
class={cls} class={cls}
v-show={this.activeSidebar == name && !this.currentIsDefSidebar} v-show={this.activeSidebar == name && !this.currentIsDefSidebar}
> >
{CacheMenuContainer.get(name)} {this.CacheMenuContainer.get(name)}
</div> </div>
); );
} }
@@ -384,7 +464,7 @@ export default {
<div class="lemon-container__title"> <div class="lemon-container__title">
<div class="lemon-container__displayname"> <div class="lemon-container__displayname">
{useScopedSlot( {useScopedSlot(
this.$scopedSlots["contact-title"], this.$scopedSlots["message-title"],
curact.displayName, curact.displayName,
curact curact
)} )}
@@ -392,6 +472,8 @@ export default {
</div> </div>
<lemon-messages <lemon-messages
ref="messages" ref="messages"
hide-time={this.hideMessageTime}
hide-name={this.hideMessageName}
time-format={this.messageTimeFormat} time-format={this.messageTimeFormat}
reverse-user-id={this.user.id} reverse-user-id={this.user.id}
on-reach-top={this._emitPullMessages} on-reach-top={this._emitPullMessages}
@@ -399,6 +481,9 @@ export default {
/> />
<lemon-editor <lemon-editor
ref="editor" ref="editor"
tools={this.editorTools}
sendText={this.sendText}
sendKey={this.sendKey}
onSend={this._handleSend} onSend={this._handleSend}
onUpload={this._handleUpload} onUpload={this._handleUpload}
/> />
@@ -421,6 +506,12 @@ export default {
<h4>{curact.displayName}</h4> <h4>{curact.displayName}</h4>
<lemon-button <lemon-button
on-click={() => { on-click={() => {
if (isEmpty(curact.lastContent)) {
this.updateContact({
id: curact.id,
lastContent: " "
});
}
this.changeContact(curact.id, DEFAULT_MENU_LASTMESSAGES); this.changeContact(curact.id, DEFAULT_MENU_LASTMESSAGES);
}} }}
> >
@@ -433,12 +524,14 @@ export default {
); );
return nodes; return nodes;
}, },
_handleSidebarScroll() {
contextmenu.hide();
},
_addContact(data, t) { _addContact(data, t) {
const type = { const type = {
0: "unshift", 0: "unshift",
1: "push" 1: "push"
}[t]; }[t];
//this.contacts[type](cloneDeep(data));
this.contacts[type](data); this.contacts[type](data);
}, },
_addMessage(data, contactId, t) { _addMessage(data, contactId, t) {
@@ -447,10 +540,8 @@ export default {
1: "push" 1: "push"
}[t]; }[t];
if (!Array.isArray(data)) data = [data]; if (!Array.isArray(data)) data = [data];
messages[contactId] = messages[contactId] || []; allMessages[contactId] = allMessages[contactId] || [];
messages[contactId][type](...data); allMessages[contactId][type](...data);
//console.log(messages[contactId]);
this.forceUpdateMessage();
}, },
/** /**
* 设置最新消息DOM * 设置最新消息DOM
@@ -461,6 +552,12 @@ export default {
lastContentRender[messageType] = render; lastContentRender[messageType] = render;
}, },
lastContentRender(message) { lastContentRender(message) {
if (!isFunction(lastContentRender[message.type])) {
console.error(
`not found '${message.type}' of the latest message renderer,try to use setLastContentRender()`
);
return "";
}
return lastContentRender[message.type].call(this, message); return lastContentRender[message.type].call(this, message);
}, },
/** /**
@@ -468,14 +565,22 @@ export default {
* @param {String} str 被替换的字符串 * @param {String} str 被替换的字符串
* @return {String} 替换后的字符串 * @return {String} 替换后的字符串
*/ */
replaceEmojiName(str) { emojiNameToImage(str) {
return str.replace(/\[!(\w+)\]/gi, (str, match) => { return str.replace(/\[!(\w+)\]/gi, (str, match) => {
const file = match; const file = match;
return emojiMap[file] return emojiMap[file]
? `<img src="${emojiMap[file]}" />` ? `<img emoji-name="${match}" src="${emojiMap[file]}" />`
: `[!${match}]`; : `[!${match}]`;
}); });
}, },
emojiImageToName(str) {
return str.replace(/<img emoji-name=\"([^\"]*?)\" [^>]*>/gi, "[!$1]");
},
updateCurrentMessages() {
if (!allMessages[this.currentContactId])
allMessages[this.currentContactId] = [];
this.currentMessages = allMessages[this.currentContactId];
},
/** /**
* 将当前聊天窗口滚动到底部 * 将当前聊天窗口滚动到底部
*/ */
@@ -486,30 +591,54 @@ export default {
* 改变聊天对象 * 改变聊天对象
* @param contactId 联系人 id * @param contactId 联系人 id
*/ */
changeContact(contactId, menuName) { async changeContact(contactId, menuName) {
if (this.currentContactId == contactId) {
this.currentContactId = undefined;
}
if (menuName) { if (menuName) {
this.changeMenu(menuName); this.changeMenu(menuName);
} else {
if (this._changeContactLock || this.currentContactId == contactId)
return false;
} }
const prevCurrentContactId = this.currentContactId;
//稿
if (prevCurrentContactId) {
const editorValue = this.getEditorValue();
if (editorValue) {
this.CacheDraft.set(prevCurrentContactId, editorValue);
this.updateContact({
id: prevCurrentContactId,
lastContent: `<span style="color:red;">[草稿]</span><span>${this.lastContentRender(
{ type: "text", content: editorValue }
)}</span>`
});
this.setEditorValue("");
}
}
this.currentContactId = contactId; this.currentContactId = contactId;
this.$emit("change-contact", this.currentContact); if (!this.currentContactId) return false;
this.$emit("change-contact", this.currentContact, this);
if (isFunction(this.currentContact.renderContainer)) { if (isFunction(this.currentContact.renderContainer)) {
return; return;
} }
if (this._menuIsMessages()) { //稿
if (!CacheMessageLoaded.has(contactId)) { const draft = this.CacheDraft.get(contactId) || "";
this.$refs.messages.resetLoadState(); if (draft) this.setEditorValue(draft);
}
if (!messages[contactId]) { if (this.CacheMessageLoaded.has(contactId)) {
this._emitPullMessages(isEnd => this.messageViewToBottom()); this.$refs.messages.loaded();
} else { } else {
setTimeout(() => { this.$refs.messages.resetLoadState();
this.messageViewToBottom(); }
}, 0);
} if (!allMessages[contactId]) {
this.updateCurrentMessages();
this._emitPullMessages(isEnd => this.messageViewToBottom());
} else {
setTimeout(() => {
this.updateCurrentMessages();
this.messageViewToBottom();
}, 0);
} }
}, },
/** /**
@@ -517,28 +646,28 @@ export default {
* @param messageId 消息 id * @param messageId 消息 id
* @param contactId 联系人 id * @param contactId 联系人 id
*/ */
removeMessage(messageId, contactId) { removeMessage(messageId) {
const index = this.findMessageIndexById(messageId, contactId); const message = this.findMessage(messageId);
if (index !== -1) { if (!message) return false;
messages[contactId].splice(index, 1); const index = allMessages[message.toContactId].findIndex(
this.forceUpdateMessage(); ({ id }) => id == messageId
} );
allMessages[message.toContactId].splice(index, 1);
return true;
}, },
/** /**
* 修改聊天一条聊天消息 * 修改聊天一条聊天消息
* @param {Message} data 根据 data.id 查找聊天消息并覆盖传入的值 * @param {Message} data 根据 data.id 查找聊天消息并覆盖传入的值
* @param contactId 联系人 id * @param contactId 联系人 id
*/ */
updateMessage(messageId, contactId, data) { updateMessage(message) {
const index = this.findMessageIndexById(messageId, contactId); if (!message.id) return false;
if (index !== -1) { let historyMessage = this.findMessage(message.id);
messages[contactId][index] = Object.assign( if (!historyMessage) return false;
messages[contactId][index], historyMessage = Object.assign(historyMessage, message, {
data toContactId: historyMessage.toContactId
); });
console.log("--------", messages[contactId][index]); return true;
this.forceUpdateMessage(messageId);
}
}, },
/** /**
* 手动更新对话消息 * 手动更新对话消息
@@ -567,6 +696,7 @@ export default {
* @param {String} name 按钮 name * @param {String} name 按钮 name
*/ */
changeMenu(name) { changeMenu(name) {
if (this._changeContactLock) return false;
this.$emit("change-menu", name); this.$emit("change-menu", name);
this.activeSidebar = name; this.activeSidebar = name;
}, },
@@ -577,11 +707,20 @@ export default {
* EmojiItem = {name: wx,title: 微笑,src: url} 无分组 * EmojiItem = {name: wx,title: 微笑,src: url} 无分组
*/ */
initEmoji(data) { initEmoji(data) {
let flatData = [];
this.$refs.editor.initEmoji(data); this.$refs.editor.initEmoji(data);
if (data[0].label) { if (data[0].label) {
data = data.flatMap(item => item.children); data.forEach(item => {
flatData.push(...item.children);
});
} else {
flatData = data;
} }
data.forEach(({ name, src }) => (emojiMap[name] = src)); flatData.forEach(({ name, src }) => (emojiMap[name] = src));
},
initEditorTools(data) {
this.editorTools = data;
this.$refs.editor.initTools(data);
}, },
/** /**
* 初始化左侧按钮 * 初始化左侧按钮
@@ -613,7 +752,7 @@ export default {
let menus = []; let menus = [];
if (Array.isArray(data)) { if (Array.isArray(data)) {
const indexMap = { const indexMap = {
lastMessages: 0, messages: 0,
contacts: 1 contacts: 1
}; };
const indexKeys = Object.keys(indexMap); const indexKeys = Object.keys(indexMap);
@@ -629,7 +768,7 @@ export default {
if (item.renderContainer) { if (item.renderContainer) {
this._customContainerReady( this._customContainerReady(
item.renderContainer, item.renderContainer,
CacheMenuContainer, this.CacheMenuContainer,
item.name item.name
); );
} }
@@ -647,7 +786,7 @@ export default {
* @param {Array<Contact>} data 联系人列表 * @param {Array<Contact>} data 联系人列表
*/ */
initContacts(data) { initContacts(data) {
this.contacts.push(...data); this.contacts = data;
this.sortContacts(); this.sortContacts();
}, },
/** /**
@@ -659,13 +798,43 @@ export default {
return a.index.localeCompare(b.index); return a.index.localeCompare(b.index);
}); });
}, },
appendContact(contact) {
if (isEmpty(contact.id) || isEmpty(contact.displayName)) {
console.error("id | displayName cant be empty");
return false;
}
if (this.hasContact(contact.id)) return true;
this.contacts.push(
Object.assign(
{
id: "",
displayName: "",
avatar: "",
index: "",
unread: 0,
lastSendTime: "",
lastSendTime: ""
},
contact
)
);
return true;
},
removeContact(id) {
const index = this.findContactIndexById(id);
if (index === -1) return false;
this.contacts.splice(index, 1);
this.CacheDraft.remove(id);
this.CacheMessageLoaded.remove(id);
return true;
},
/** /**
* 修改联系人数据 * 修改联系人数据
* @param {Contact} data 修改的数据根据 data.id 查找联系人并覆盖传入的值 * @param {Contact} data 修改的数据根据 Contact.id 查找联系人并覆盖传入的值
*/ */
updateContact(contactId, data) { updateContact(data) {
const contactId = data.id;
delete data.id; delete data.id;
delete data.toContactId;
const index = this.findContactIndexById(contactId); const index = this.findContactIndexById(contactId);
if (index !== -1) { if (index !== -1) {
@@ -690,16 +859,22 @@ export default {
findContactIndexById(contactId) { findContactIndexById(contactId) {
return this.contacts.findIndex(item => item.id == contactId); return this.contacts.findIndex(item => item.id == contactId);
}, },
findMessageIndexById(messageId, contactId) { /**
const msg = messages[contactId]; * 根据 id 查找判断是否存在联系人
if (isEmpty(msg)) { * @param contactId 联系人 id
return -1; * @return {Boolean}
} */
return msg.findIndex(item => item.id == messageId); hasContact(contactId) {
return this.findContactIndexById(contactId) !== -1;
}, },
findMessageById(messageId, contactId) { findMessage(messageId) {
const index = this.findMessageIndexById(messageId, contactId); for (const key in allMessages) {
if (index !== -1) return messages[contactId][index]; const message = allMessages[key].find(({ id }) => id == messageId);
if (message) return message;
}
},
findContact(contactId) {
return this.getContacts().find(({ id }) => id == contactId);
}, },
/** /**
* 返回所有联系人 * 返回所有联系人
@@ -708,36 +883,26 @@ export default {
getContacts() { getContacts() {
return this.contacts; return this.contacts;
}, },
//
getCurrentContact() {
return this.currentContact;
},
getCurrentMessages() {
return this.currentMessages;
},
setEditorValue(val) {
if (!isString(val)) return false;
this.$refs.editor.setValue(this.emojiNameToImage(val));
},
getEditorValue() {
return this.$refs.editor.getFormatValue();
},
/** /**
* 返回所有消息 * 返回所有消息
* @return {Object<Contact.id,Message>} * @return {Object<Contact.id,Message>}
*/ */
getMessages(contactId) { getMessages(contactId) {
return (contactId ? messages[contactId] : messages) || []; return (contactId ? allMessages[contactId] : allMessages) || [];
},
// appendContact(data) {
// this._addContact(data, 0);
// },
// prependContact(data) {
// this._addContact(data, 1);
// },
// addContactMessage(data) {
// this._addContact(data, 0);
// },
// prependContactMessage(data) {
// this._addContact(data, 1);
// },
// appendMessage(data) {},
// prependMessage(data) {},
// removeContact(contactId) {},
// removeContactMessage(contactId) {},
// removeContactAll(contactId) {},
/**
* 将自定义的HTML显示在主窗口内
*/
openrenderContainer(vnode) {
//renderContainerQueue[this.activeSidebar] = vnode;
//this.$slots._renderContainer = vnode;
}, },
changeDrawer(render) { changeDrawer(render) {
this.drawerVisible = !this.drawerVisible; this.drawerVisible = !this.drawerVisible;
@@ -754,16 +919,14 @@ export default {
}; };
</script> </script>
<style lang="stylus"> <style lang="stylus">
wrapper-width = 850px
drawer-width = 200px drawer-width = 200px
bezier = cubic-bezier(0.645, 0.045, 0.355, 1) bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
@import '~styles/utils/index' @import '~styles/utils/index'
+b(lemon-wrapper) +b(lemon-wrapper)
width wrapper-width
height 580px
display flex display flex
font-size 14px font-size 14px
font-family "Microsoft YaHei"
//mask-image radial-gradient(circle, white 100%, black 100%) //mask-image radial-gradient(circle, white 100%, black 100%)
background #efefef background #efefef
transition all .4s bezier transition all .4s bezier
@@ -812,13 +975,18 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
+b(lemon-sidebar) +b(lemon-sidebar)
width 250px width 250px
background #efefef background #efefef
overflow-y auto display flex
scrollbar-light() flex-direction column
+e(scroll){
overflow-y auto
scrollbar-light()
}
+e(label) +e(label)
padding 6px 14px 6px 14px padding 6px 14px 6px 14px
color #666 color #666
font-size 12px font-size 12px
margin 0 margin 0
text-align left
+b(lemon-contact--active) +b(lemon-contact--active)
background #d9d9d9 background #d9d9d9
+b(lemon-container) +b(lemon-container)
@@ -827,7 +995,7 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
background #f4f4f4 background #f4f4f4
word-break() word-break()
position relative position relative
z-index 2 z-index 10
+e(title) +e(title)
padding 15px 15px padding 15px 15px
+e(displayname) +e(displayname)
@@ -842,11 +1010,10 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
overflow hidden overflow hidden
background #f4f4f4 background #f4f4f4
transition width .4s bezier transition width .4s bezier
z-index 1 z-index 9
width drawer-width width drawer-width
height 100% height 100%
box-sizing border-box box-sizing border-box
//border-left 1px solid #e9e9e9
+b(lemon-wrapper) +b(lemon-wrapper)
+m(drawer-show) +m(drawer-show)
+b(lemon-drawer) +b(lemon-drawer)
@@ -861,4 +1028,77 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
font-weight normal font-weight normal
margin 10px 0 20px 0 margin 10px 0 20px 0
user-select none user-select none
.lemon-wrapper--theme-blue
.lemon-message__content
background #f3f3f3
&::before
border-right-color #f3f3f3
.lemon-message--reverse .lemon-message__content
background #e6eeff
&::before
border-left-color #e6eeff
.lemon-container
background #fff
.lemon-sidebar
background #f9f9f9
.lemon-contact
background #f9f9f9
&:hover:not(.lemon-contact--active)
background #f1f1f1
&--active
background #e9e9e9
.lemon-menu
background #096bff
.lemon-menu__item
color rgba(255,255,255,0.4)
&:hover:not(.lemon-menu__item--active)
color rgba(255,255,255,0.6)
&--active
color #fff
text-shadow 0 0 10px rgba(2,48,118,0.4)
.lemon-wrapper--simple
.lemon-menu
.lemon-sidebar
display none
.lemon-wrapper--simple
.lemon-menu
.lemon-sidebar
display none
+b(lemon-contextmenu)
border-radius 4px
font-size 14px
font-variant tabular-nums
line-height 1.5
color rgba(0, 0, 0, 0.65)
z-index 10
background-color #fff
border-radius 6px
box-shadow 0 2px 8px rgba(0, 0, 0, 0.06)
position absolute
transform-origin 50% 150%
box-sizing border-box
user-select none
overflow hidden
min-width 120px
+e(item)
font-size 14px
line-height 16px
padding 10px 15px
cursor pointer
display flex
align-items center
color #333
> span
display inline-block
flex none
//max-width 100px
ellipsis()
&:hover
background #f3f3f3
color #000
&:active
background #e9e9e9
+e(icon)
font-size 16px
margin-right 4px
</style> </style>
+95 -50
View File
@@ -1,8 +1,17 @@
<script> <script>
import { useScopedSlot } from "utils";
export default { export default {
name: "lemonMessageBasic", name: "lemonMessageBasic",
inject: ["IMUI"], inject: {
IMUI: {
from: "IMUI",
default() {
return this;
}
}
},
props: { props: {
contextmenu: Array,
message: { message: {
type: Object, type: Object,
default: () => { default: () => {
@@ -14,7 +23,8 @@ export default {
default: () => "" default: () => ""
}, },
reverse: Boolean, reverse: Boolean,
hiddenTitle: Boolean hideName: Boolean,
hideTime: Boolean
}, },
data() { data() {
return {}; return {};
@@ -25,9 +35,10 @@ export default {
<div <div
class={[ class={[
"lemon-message", "lemon-message",
`lemon-message--status-${status}`,
{ {
"lemon-message--reverse": this.reverse, "lemon-message--reverse": this.reverse,
"lemon-message--hidden-title": this.hiddenTitle "lemon-message--hide-name": this.hideName
} }
]} ]}
> >
@@ -50,23 +61,50 @@ export default {
> >
{fromUser.displayName} {fromUser.displayName}
</span> </span>
<span class="lemon-message__time">{this.timeFormat(sendTime)}</span> {this.hideTime == true && (
<span
class="lemon-message__time"
on-click={e => {
this._emitClick(e, "sendTime");
}}
>
{this.timeFormat(sendTime)}
</span>
)}
</div> </div>
<div <div class="lemon-message__content-flex">
class="lemon-message__content" <div
on-click={e => { v-lemon-contextmenu_message={this.IMUI.contextmenu}
this._emitClick(e, "content"); class="lemon-message__content"
}} on-click={e => {
> this._emitClick(e, "content");
{this.useScopedSlots("content", this.message)} }}
</div> >
<div {useScopedSlot(this.$scopedSlots["content"], null, this.message)}
class="lemon-message__status" </div>
on-click={e => { <div class="lemon-message__content-after">
this._emitClick(e, "status"); {useScopedSlot(
}} this.IMUI.$scopedSlots["message-after"],
> null,
{this._renderStatue(status)} this.message
)}
</div>
<div
class="lemon-message__status"
on-click={e => {
this._emitClick(e, "status");
}}
>
<i class="lemon-icon-loading lemonani-spin" />
<i
class="lemon-icon-prompt"
title="重发消息"
style={{
color: "#ff2525",
cursor: "pointer"
}}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -78,29 +116,7 @@ export default {
watch: {}, watch: {},
methods: { methods: {
_emitClick(e, key) { _emitClick(e, key) {
this.IMUI.$emit("message-click", e, key, this.message); this.IMUI.$emit("message-click", e, key, this.message, this.IMUI);
},
_renderStatue(status) {
if (status == "going") {
return <i class="lemon-icon-loading lemonani-spin" />;
} else if (status == "failed") {
return (
<i
class="lemon-icon-prompt"
title="重发消息"
style={{
color: "#ff2525",
cursor: "pointer"
}}
/>
);
}
return;
},
useScopedSlots(name, params, defVnode = "", context = this) {
return context.$scopedSlots[name]
? context.$scopedSlots[name](params)
: defVnode;
} }
} }
}; };
@@ -118,8 +134,8 @@ arrow()
display flex display flex
padding 10px 0 padding 10px 0
+e(time) +e(time)
color #bbb color #b9b9b9
padding 0 4px padding 0 5px
+e(inner) +e(inner)
position relative position relative
+e(avatar) +e(avatar)
@@ -133,7 +149,9 @@ arrow()
line-height 14px line-height 14px
padding-bottom 6px padding-bottom 6px
user-select none user-select none
color #999 color #666
+e(content-flex)
display flex
+e(content) +e(content)
font-size 14px font-size 14px
line-height 20px line-height 20px
@@ -141,7 +159,7 @@ arrow()
background #fff background #fff
border-radius 4px border-radius 4px
position relative position relative
margin 0 46px 0 0 margin 0
img img
video video
background #e9e9e9 background #e9e9e9
@@ -151,22 +169,49 @@ arrow()
left -4px left -4px
border-left none border-left none
border-right-color #fff border-right-color #fff
+e(content-after)
display block
width 48px
height 36px
padding-left 6px
flex none
font-size 12px
color #aaa
overflow hidden
visibility hidden
+e(status) +e(status)
position absolute position absolute
top 23px top 23px
right 20px right 20px
color #aaa color #aaa
font-size 20px font-size 20px
.lemon-icon-loading
.lemon-icon-prompt
display none
+m(status-going)
.lemon-icon-loading
display inline-block
+m(status-failed)
.lemon-icon-prompt
display inline-block
+m(status-succeed)
+e(content-after)
visibility visible
+m(reverse) +m(reverse)
flex-direction row-reverse flex-direction row-reverse
+e(content-flex)
flex-direction row-reverse
+e(content-after)
padding-right 6px
padding-left 0
text-align right
+e(title) +e(title)
flex-direction row-reverse flex-direction row-reverse
+e(status) +e(status)
left 20px left 26px
right auto right auto
+e(content) +e(content)
background #35d863 background #35d863
margin 0 0 0 46px
&:before &:before
arrow() arrow()
left auto left auto
@@ -178,9 +223,9 @@ arrow()
+e(avatar) +e(avatar)
padding-right 0 padding-right 0
padding-left 10px padding-left 10px
+m(hidden-title) +m(hide-name)
+e(status) +e(status)
top 7px top 3px
+e(title) +e(title)
display none display none
+e(content) +e(content)
+12 -1
View File
@@ -2,13 +2,24 @@
export default { export default {
name: "lemonMessageEvent", name: "lemonMessageEvent",
inheritAttrs: false, inheritAttrs: false,
inject: ["IMUI"],
render() { render() {
const { content } = this.$attrs.message; const { content } = this.$attrs.message;
return ( return (
<div class="lemon-message lemon-message-event"> <div class="lemon-message lemon-message-event">
<span class="lemon-message-event__content">{content}</span> <span
class="lemon-message-event__content"
on-click={e => this._emitClick(e, "content")}
>
{content}
</span>
</div> </div>
); );
},
methods: {
_emitClick(e, key) {
this.IMUI.$emit("message-click", e, key, this.$attrs.message, this.IMUI);
}
} }
}; };
</script> </script>
+3 -1
View File
@@ -10,7 +10,7 @@ export default {
props={{ ...this.$attrs }} props={{ ...this.$attrs }}
scopedSlots={{ scopedSlots={{
content: props => { content: props => {
const content = this.IMUI.replaceEmojiName(props.content); const content = this.IMUI.emojiNameToImage(props.content);
return <span domProps={{ innerHTML: content }} />; return <span domProps={{ innerHTML: content }} />;
} }
}} }}
@@ -29,6 +29,8 @@ export default {
height 18px height 18px
display inline-block display inline-block
background transparent background transparent
position relative
top -1px
padding 0 2px padding 0 2px
vertical-align middle vertical-align middle
</style> </style>
+22 -13
View File
@@ -1,9 +1,14 @@
<script> <script>
import { hoursTimeFormat } from "utils"; import { hoursTimeFormat } from "utils";
import contextmenu from "../directives/contextmenu";
export default { export default {
name: "LemonMessages", name: "LemonMessages",
components: {}, components: {},
props: { props: {
//
hideName: Boolean,
//
hideTime: Boolean,
reverseUserId: String, reverseUserId: String,
timeRange: { timeRange: {
type: Number, type: Number,
@@ -52,24 +57,26 @@ export default {
message: { message: {
id: "__time__", id: "__time__",
type: "event", type: "event",
content: this.timeFormat(message.sendTime) content: hoursTimeFormat(message.sendTime)
} }
}} }}
/> />
); );
} }
node.push(
<tagName let attrs;
ref="message" if (message.type == "event") {
refInFor={true} attrs = { message: message };
attrs={{ } else {
timeFormat: this.msecRange > 0 ? () => {} : this.timeFormat, attrs = {
message: message, timeFormat: this.timeFormat,
reverse: this.reverseUserId == message.fromUser.id, message: message,
hiddenTitle: false reverse: this.reverseUserId == message.fromUser.id,
}} hideTime: this.hideTime,
/> hideName: this.hideName
); };
}
node.push(<tagName ref="message" refInFor={true} attrs={attrs} />);
return node; return node;
})} })}
</div> </div>
@@ -90,6 +97,7 @@ export default {
}, },
loaded() { loaded() {
this._loadend = true; this._loadend = true;
this.$forceUpdate();
}, },
resetLoadState() { resetLoadState() {
this._loading = false; this._loading = false;
@@ -97,6 +105,7 @@ export default {
}, },
async _handleScroll(e) { async _handleScroll(e) {
const { target } = e; const { target } = e;
contextmenu.hide();
if ( if (
target.scrollTop == 0 && target.scrollTop == 0 &&
this._loading == false && this._loading == false &&
+11 -8
View File
@@ -1,6 +1,6 @@
<script> <script>
const popoverCloseQueue = []; const popoverCloseQueue = [];
const popoverCloseAll = () => popoverCloseQueue.forEach(callback => callback()); import contextmenu from "../directives/contextmenu";
const triggerEvents = { const triggerEvents = {
hover(el) {}, hover(el) {},
focus(el) { focus(el) {
@@ -14,6 +14,7 @@ const triggerEvents = {
click(el) { click(el) {
el.addEventListener("click", e => { el.addEventListener("click", e => {
e.stopPropagation(); e.stopPropagation();
contextmenu.hide();
this.changeVisible(); this.changeVisible();
}); });
}, },
@@ -51,7 +52,7 @@ export default {
render() { render() {
return ( return (
<span style="position:relative"> <span style="position:relative">
<transition name="slide-top"> <transition name="lemon-slide-top">
{this.visible && ( {this.visible && (
<div <div
class="lemon-popover" class="lemon-popover"
@@ -59,7 +60,6 @@ export default {
style={this.popoverStyle} style={this.popoverStyle}
on-click={e => e.stopPropagation()} on-click={e => e.stopPropagation()}
> >
<div class="lemon-popover__title" />
<div class="lemon-popover__content">{this.$slots.content}</div> <div class="lemon-popover__content">{this.$slots.content}</div>
<div class="lemon-popover__arrow" /> <div class="lemon-popover__arrow" />
</div> </div>
@@ -96,9 +96,12 @@ export default {
this.visible ? this.close() : this.open(); this.visible ? this.close() : this.open();
}, },
open() { open() {
popoverCloseAll(); this.closeAll();
this.visible = true; this.visible = true;
}, },
closeAll() {
popoverCloseQueue.forEach(callback => callback());
},
close() { close() {
this.visible = false; this.visible = false;
} }
@@ -117,7 +120,7 @@ export default {
z-index 10 z-index 10
background-color #fff background-color #fff
border-radius 4px border-radius 4px
box-shadow 0 2px 8px rgba(0, 0, 0, 0.15) box-shadow 0 2px 8px rgba(0, 0, 0, 0.08)
position absolute position absolute
transform-origin 50% 150% transform-origin 50% 150%
+e(content) +e(content)
@@ -135,9 +138,9 @@ export default {
width 8px width 8px
height 8px height 8px
background #fff background #fff
.slide-top-leave-active ,.slide-top-enter-active .lemon-slide-top-leave-active ,.lemon-slide-top-enter-active
transition all .3s cubic-bezier(0.645, 0.045, 0.355, 1) transition all .2s cubic-bezier(0.645, 0.045, 0.355, 1)
.slide-top-enter, .slide-top-leave-to .lemon-slide-top-enter, .lemon-slide-top-leave-to
transform translateY(-10px) scale(.8) transform translateY(-10px) scale(.8)
opacity 0 opacity 0
</style> </style>
+75
View File
@@ -0,0 +1,75 @@
import Vue from "vue";
import { isFunction, isEmpty } from "utils/validate";
import LemonPopover from "../components/popover.vue";
let popover;
const hidePopover = () => {
if (popover) popover.style.display = "none";
};
const showPopover = () => {
if (popover) popover.style.display = "block";
};
document.addEventListener("click", e => {
hidePopover();
});
export default {
hide: hidePopover,
bind(el, binding, vnode) {
el.addEventListener("contextmenu", e => {
if (isEmpty(binding.value) || !Array.isArray(binding.value)) return;
e.preventDefault();
LemonPopover.methods.closeAll();
let component;
let visibleItems = [];
if (binding.modifiers.message) component = vnode.context;
else if (binding.modifiers.contact) component = vnode.child;
if (!popover) {
popover = document.createElement("div");
popover.className = "lemon-contextmenu";
document.body.appendChild(popover);
}
popover.innerHTML = binding.value
.map(item => {
let visible;
if (isFunction(item.visible)) {
visible = item.visible(component);
} else {
visible = item.visible === undefined ? true : item.visible;
}
if (visible) {
visibleItems.push(item);
const icon = item.icon
? `<i class="lemon-contextmenu__icon ${item.icon}"></i>`
: "";
return `<div style="color:${item.color}" title="${item.text}" class="lemon-contextmenu__item">${icon}<span>${item.text}</span></div>`;
}
return "";
})
.join("");
popover.style.top = `${e.pageY}px`;
popover.style.left = `${e.pageX}px`;
popover.childNodes.forEach((node, index) => {
const { click, render } = visibleItems[index];
node.addEventListener("click", e => {
e.stopPropagation();
if (isFunction(click)) click(e, component, hidePopover);
});
if (isFunction(render)) {
const ins = Vue.extend({
render: h => {
return render(h, component, hidePopover);
}
});
const renderComponent = new ins().$mount();
node.querySelector("span").innerHTML = renderComponent.$el.outerHTML;
}
});
showPopover();
});
},
inserted(el, binding, vnode) {}
};
+3 -1
View File
@@ -1,3 +1,4 @@
import Contextmenu from "./directives/contextmenu";
import LemonTabs from "./components/tabs"; import LemonTabs from "./components/tabs";
import LemonPopover from "./components/popover"; import LemonPopover from "./components/popover";
import LemonButton from "./components/button"; import LemonButton from "./components/button";
@@ -14,7 +15,7 @@ import lemonMessageEvent from "./components/message/event";
import LemonIMUI from "./components/index"; import LemonIMUI from "./components/index";
import "./styles/common/index.styl"; import "./styles/common/index.styl";
const version = "0.1"; const version = "1.4.2";
const components = [ const components = [
LemonIMUI, LemonIMUI,
LemonContact, LemonContact,
@@ -32,6 +33,7 @@ const components = [
lemonMessageEvent lemonMessageEvent
]; ];
const install = (Vue, opts = {}) => { const install = (Vue, opts = {}) => {
Vue.directive("LemonContextmenu", Contextmenu);
components.forEach(component => { components.forEach(component => {
Vue.component(component.name, component); Vue.component(component.name, component);
}); });
+6 -8
View File
@@ -1,17 +1,15 @@
import {clearHtml } from 'utils';
export default { export default {
voice(message) {
return "[语音]";
},
file(message) { file(message) {
return "[文件]"; return "[文件]";
}, },
video(message) {
return "[视频]";
},
image(message) { image(message) {
return "[图片]"; return "[图片]";
}, },
text(message) { text(message) {
return this.replaceEmojiName(message.content); return this.emojiNameToImage(clearHtml(message.content));
} },
event(message){
return '[通知]';
},
}; };
+10 -7
View File
@@ -1,12 +1,15 @@
// @font-face {
// font-family: 'lemon-icons';
// src: url('//at.alicdn.com/t/font_1312162_neqltsj20an.eot');
// src: url('//at.alicdn.com/t/font_1312162_neqltsj20an.eot?#iefix') format('embedded-opentype'),
// url('//at.alicdn.com/t/font_1312162_neqltsj20an.woff2') format('woff2'),
// url('//at.alicdn.com/t/font_1312162_neqltsj20an.woff') format('woff'),
// url('//at.alicdn.com/t/font_1312162_neqltsj20an.ttf') format('truetype'),
// url('//at.alicdn.com/t/font_1312162_neqltsj20an.svg#iconfont') format('svg');
// }
@font-face { @font-face {
font-family: 'lemon-icons'; font-family: 'lemon-icons';
src: url('//at.alicdn.com/t/font_1312162_neqltsj20an.eot'); src:url('../fonts/icon.woff') format('woff');
src: url('//at.alicdn.com/t/font_1312162_neqltsj20an.eot?#iefix') format('embedded-opentype'),
url('//at.alicdn.com/t/font_1312162_neqltsj20an.woff2') format('woff2'),
url('//at.alicdn.com/t/font_1312162_neqltsj20an.woff') format('woff'),
url('//at.alicdn.com/t/font_1312162_neqltsj20an.ttf') format('truetype'),
url('//at.alicdn.com/t/font_1312162_neqltsj20an.svg#iconfont') format('svg');
} }
[class^='lemon-icon-'], [class^='lemon-icon-'],
[class*=' lemon-icon-'] [class*=' lemon-icon-']
Binary file not shown.
-15
View File
@@ -22,18 +22,3 @@ export default class MemoryCache {
return !!this.table[key]; return !!this.table[key];
} }
} }
// export default {
// data: {},
// get(name) {
// console.log(this.data);
// }
// };
// class MemoryCache {
// constructor() {
// super();
// }
// get($name) {
// console.log(1);
// }
// }
// export default MemoryCache;
+1 -1
View File
@@ -1,6 +1,6 @@
export const EMIT_AVATAR_CLICK = "avatar-click"; export const EMIT_AVATAR_CLICK = "avatar-click";
export const DEFAULT_MENU_LASTMESSAGES = "lastMessages"; export const DEFAULT_MENU_LASTMESSAGES = "messages";
export const DEFAULT_MENU_CONTACTS = "contacts"; export const DEFAULT_MENU_CONTACTS = "contacts";
export const DEFAULT_MENUS = [DEFAULT_MENU_LASTMESSAGES, DEFAULT_MENU_CONTACTS]; export const DEFAULT_MENUS = [DEFAULT_MENU_LASTMESSAGES, DEFAULT_MENU_CONTACTS];
/** /**
+9 -5
View File
@@ -53,7 +53,7 @@ export function timeFormat(t, format) {
return format; return format;
} }
export function fastDone(event, callback) { export function funCall(event, callback) {
if (isFunction(event)) { if (isFunction(event)) {
event(() => { event(() => {
callback(); callback();
@@ -70,7 +70,14 @@ export function fastDone(event, callback) {
export function arrayIntersect(a, b) { export function arrayIntersect(a, b) {
return a.filter(x => b.includes(x)); return a.filter(x => b.includes(x));
} }
//清除字符串内的所有HTML标签
export function clearHtml(str){
return str.replace(/<.*?>/ig,"");
}
//清除字符串内的所有HTML标签,除了IMG
export function clearHtmlExcludeImg(str){
return str.replace(/<(?!img).*?>/ig, "");
}
export function error(text) { export function error(text) {
throw new Error(text); throw new Error(text);
} }
@@ -96,9 +103,6 @@ export function mergeDeep(o1, o2) {
return o1; return o1;
} }
export function toEmojiName(str) {
return str.replace(/<img emoji-name=\"([^\"]*?)\" [^>]*>/gi, "[!$1]");
}
export function formatByte(value) { export function formatByte(value) {
if (null == value || value == "") { if (null == value || value == "") {
return "0 Bytes"; return "0 Bytes";