修复自定义指令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
+10 -1
View File
@@ -1,12 +1,19 @@
<script>
export default {
name: "LemonAvatar",
inject: ["IMUI"],
props: {
src: String,
icon: {
type: String,
default: "lemon-icon-people"
},
circle: {
type: Boolean,
default() {
return this.IMUI ? this.IMUI.avatarCricle : false;
}
},
size: {
type: Number,
default: 32
@@ -21,7 +28,7 @@ export default {
return (
<span
style={this.style}
class="lemon-avatar"
class={["lemon-avatar", { "lemon-avatar--circle": this.circle }]}
on-click={e => this.$emit("click", e)}
>
{this.imageFinishLoad && <i class={this.icon} />}
@@ -65,6 +72,8 @@ export default {
overflow hidden
vertical-align middle
border-radius 4px
+m(circle)
border-radius 50%
img
width 100%
height 100%
+21 -10
View File
@@ -2,12 +2,16 @@
export default {
name: "LemonButton",
props: {
color: {
type: String,
default: "default"
},
disabled: Boolean
},
render() {
return (
<button
class="lemon-button"
class={["lemon-button", `lemon-button--color-${this.color}`]}
disabled={this.disabled}
type="button"
on-click={this._handleClick}
@@ -47,13 +51,20 @@ export default {
background-color #fff
box-shadow 0 2px 0 rgba(0, 0, 0, 0.015)
text-shadow 0 -1px 0 rgba(0, 0, 0, 0.12)
&:hover:not([disabled])
border-color #666
color #333
&:active
background-color #ddd
&[disabled]
cursor not-allowed
color #aaa
background #eee
+m(color-default)
&:hover:not([disabled])
border-color #666
color #333
&:active
background-color #ddd
&[disabled]
cursor not-allowed
color #aaa
background #eee
+m(color-grey)
background #e1e1e1
border-color #e1e1e1
color #666
&:hover:not([disabled])
border-color #bbb
</style>
+36 -26
View File
@@ -1,9 +1,17 @@
<script>
import { isString, isToday } from "utils/validate";
import { timeFormat } from "utils";
import { timeFormat, useScopedSlot } from "utils";
export default {
name: "LemonContact",
components: {},
inject: {
IMUI: {
from: "IMUI",
default() {
return this;
}
}
},
data() {
return {};
},
@@ -18,23 +26,33 @@ export default {
}
},
render() {
const { contact } = this;
return (
<div
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
count={!this.simple ? contact.unread : 0}
class="lemon-contact__avatar"
native-on-click={e => this._handleBubbleClick(e, contact)}
>
<lemon-avatar
size={40}
native-on-click={e => this._handleAvatarClick(e, contact)}
src={contact.avatar}
/>
</lemon-badge>
<lemon-avatar size={40} src={contact.avatar} />
</lemon-badge>,
<div class="lemon-contact__inner">
<p class="lemon-contact__label">
<span class="lemon-contact__name">{contact.displayName}</span>
@@ -54,24 +72,10 @@ export default {
</p>
)}
</div>
</div>
);
},
created() {},
mounted() {},
computed: {},
watch: {},
methods: {
];
},
_handleClick(e, 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
overflow hidden
background #efefef
text-align left
p
margin 0
+m(active)
@@ -121,12 +126,17 @@ export default {
+e(content)
font-size 12px
color #999
height 18px
line-height 18px
margin-top 1px !important
ellipsis()
img
height 14px
display inline-block
vertical-align middle
margin 0 1px
position relative
top -1px
+m(name-center)
+e(label)
padding-bottom 0
+252 -56
View File
@@ -1,31 +1,118 @@
<script>
import { toEmojiName } from "utils";
import { toEmojiName, useScopedSlot, clearHtmlExcludeImg } from "utils";
const exec = (val, command = "insertHTML") => {
document.execCommand(command, false, val);
};
const selection = window.getSelection();
let lastSelectionRange;
let emojiData = [];
let isInitTool = false;
export default {
name: "LemonEditor",
inject: {
IMUI: {
from: "IMUI",
default() {
return this;
}
}
},
components: {},
props: {},
props: {
tools: {
type: Array,
default: () => []
},
sendText: {
type: String,
default: "发 送"
},
sendKey: {
type: Function,
default(e) {
return e.keyCode == 13 && e.ctrlKey === true;
}
}
},
data() {
this.clipboardBlob = null;
return {
//剪切板图片URL
clipboardUrl: "",
submitDisabled: true,
proxyTools: [],
accept: ""
};
},
created() {},
mounted() {
//this.$refs.fileInput.addEventListener("change", this._handleChangeFile);
created() {
if (this.tools && this.tools.length > 0) {
this.initTools(this.tools);
} else {
this.initTools([
{ name: "emoji" },
{ name: "uploadFile" },
{ name: "uploadImage" }
]);
}
this.IMUI.$on("change-contact", () => {
this.closeClipboardImage();
});
},
computed: {},
watch: {},
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 (
<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
style="display:none"
type="file"
@@ -35,26 +122,8 @@ export default {
onChange={this._handleChangeFile}
/>
<div class="lemon-editor__tool">
{emojiData.length > 0 && (
<lemon-popover class="lemon-editor__emoji">
<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 class="lemon-editor__tool-left">{toolLeft}</div>
<div class="lemon-editor__tool-right">{toolRight}</div>
</div>
<div class="lemon-editor__inner">
<div
@@ -65,18 +134,22 @@ export default {
on-keydown={this._handleKeydown}
on-paste={this._handlePaste}
on-click={this._handleClick}
on-input={this._handleInput}
spellcheck="false"
/>
</div>
<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">
<lemon-button
disabled={this.submitDisabled}
on-click={this._handleSend}
>
{this.sendText}
</lemon-button>
</div>
</div>
@@ -84,6 +157,68 @@ export default {
);
},
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() {
lastSelectionRange = selection.getRangeAt(0);
},
@@ -97,9 +232,6 @@ export default {
_handleClick() {
this._saveLastRange();
},
_handleInput() {
this._checkSubmitDisabled();
},
_renderEmojiTabs() {
const renderImageGrid = items => {
return items.map(item => (
@@ -131,45 +263,65 @@ export default {
_handleSelectEmoji(item) {
this._focusLastRange();
exec(`<img emoji-name="${item.name}" src="${item.src}"></img>`);
this._checkSubmitDisabled();
this._saveLastRange();
},
async _handleSelectFile(accept) {
async selectFile(accept) {
this.accept = accept;
await this.$nextTick();
this.$refs.fileInput.click();
},
_handlePaste(e) {
e.preventDefault();
const { clipboardData } = e;
const text = clipboardData.getData("text");
exec(text, "insertText");
// Array.from(clipboardData.items).forEach(item => {
// console.log(item.type);
// });
//e.target.innerText = text;
const clipboardData = e.clipboardData || window.clipboardData;
const text = clipboardData.getData("Text");
if (text) {
if (window.clipboardData) {
this.$refs.textarea.innerHTML = text;
} else {
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) {
this._saveLastRange();
//this._checkSubmitDisabled();
this._checkSubmitDisabled();
},
_handleKeydown(e) {
const { keyCode } = e;
if (keyCode == 13) {
// e.preventDefault();
// document.execCommand("defaultParagraphSeparator", false, false);
// exec("<br>");
if (this.submitDisabled == false && this.sendKey(e)) {
this._handleSend();
}
},
getFormatValue() {
return toEmojiName(
this.$refs.textarea.innerHTML
.replace(/<br>|<\/br>/, "")
.replace(/<div>|<p>/g, "\r\n")
.replace(/<\/div>|<\/p>/g, "")
);
// return toEmojiName(
// this.$refs.textarea.innerHTML
// .replace(/<br>|<\/br>/, "")
// .replace(/<div>|<p>/g, "\r\n")
// .replace(/<\/div>|<\/p>/g, "")
// );
return this.IMUI.emojiImageToName(this.$refs.textarea.innerHTML);
},
_checkSubmitDisabled() {
this.submitDisabled = !this.$refs.textarea.innerHTML.trim();
this.submitDisabled = !clearHtmlExcludeImg(
this.$refs.textarea.innerHTML.trim()
);
},
_handleSend(e) {
const text = this.getFormatValue();
@@ -190,6 +342,10 @@ export default {
initEmoji(data) {
emojiData = data;
this.$forceUpdate();
},
setValue(val) {
this.$refs.textarea.innerHTML = this.IMUI.emojiNameToImage(val);
this._checkSubmitDisabled();
}
}
};
@@ -199,28 +355,66 @@ export default {
gap = 10px;
+b(lemon-editor)
height 200px
position relative
flex-column()
+e(tool)
display flex
height 40px
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)
cursor pointer
padding 4px gap
height 28px
line-height 24px;
color #999
transition all ease .3s
font-size 12px
[class^='lemon-icon-']
line-height 26px
font-size 22px
&:hover
color #333
+m(right){
margin-left:auto;
}
+e(inner)
flex 1
overflow-x hidden
overflow-y auto
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)
height 100%
box-sizing border-box
@@ -234,6 +428,8 @@ gap = 10px;
height 20px
padding 0 2px
pointer-events none
position relative
top -1px
vertical-align middle
+e(footer)
display flex
+374 -134
View File
@@ -1,6 +1,7 @@
<script>
import { useScopedSlot, fastDone, generateUUID } from "utils";
import { useScopedSlot, funCall, generateUUID, cloneDeep } from "utils";
import { isFunction, isString, isEmpty } from "utils/validate";
import contextmenu from "../directives/contextmenu";
import {
DEFAULT_MENUS,
DEFAULT_MENU_LASTMESSAGES,
@@ -10,11 +11,7 @@ import lastContentRender from "../lastContentRender";
import MemoryCache from "utils/cache/memory";
const CacheContactContainer = new MemoryCache();
const CacheMenuContainer = new MemoryCache();
const CacheMessageLoaded = new MemoryCache();
const messages = {};
const allMessages = {};
const emojiMap = {};
let renderDrawerContent = () => {};
@@ -26,6 +23,22 @@ export default {
};
},
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
},
/**
* 初始化时是否隐藏导航按钮上的头像
* 是否隐藏导航按钮上的头像
*/
hideMenuAvatar: Boolean,
hideMenu: Boolean,
/**
* 是否隐藏消息列表内的联系人名字
*/
hideMessageName: Boolean,
/**
* 是否隐藏消息列表内的发送时间
*/
hideMessageTime: Boolean,
sendKey: Function,
sendText: String,
contextmenu: Array,
contactContextmenu: Array,
avatarCricle: Boolean,
user: {
type: Object,
default: () => {
@@ -54,12 +80,18 @@ export default {
}
},
data() {
this.CacheContactContainer = new MemoryCache();
this.CacheMenuContainer = new MemoryCache();
this.CacheMessageLoaded = new MemoryCache();
this.CacheDraft = new MemoryCache();
return {
drawerVisible: !this.hideDrawer,
currentContactId: "",
currentContactId: null,
currentMessages: [],
activeSidebar: DEFAULT_MENU_LASTMESSAGES,
contacts: [],
menus: []
menus: [],
editorTools: []
};
},
@@ -79,9 +111,6 @@ export default {
await this.$nextTick();
},
computed: {
currentMessages() {
return messages[this.currentContactId] || [];
},
currentContact() {
return this.contacts.find(item => item.id == this.currentContactId) || {};
},
@@ -124,9 +153,34 @@ export default {
...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) {
this.$emit(
@@ -134,20 +188,21 @@ export default {
message,
(replaceMessage = { status: "succeed" }) => {
next();
message = Object.assign(message, replaceMessage);
this.forceUpdateMessage(message.id);
this.updateMessage(Object.assign(message, replaceMessage));
},
file
);
},
_handleSend(text) {
const message = this._createMessage({ content: text });
this.appendMessage(message);
this.appendMessage(message, true);
this._emitSend(message, () => {
this.updateContact(message.toContactId, {
this.updateContact({
id: message.toContactId,
lastContent: this.lastContentRender(message),
lastSendTime: message.sendTime
});
this.CacheDraft.remove(message.toContactId);
});
},
_handleUpload(file) {
@@ -167,11 +222,12 @@ export default {
};
}
const message = this._createMessage(joinMessage);
this.appendMessage(message);
this.appendMessage(message, true);
this._emitSend(
message,
() => {
this.updateContact(message.toContactId, {
this.updateContact({
id: message.toContactId,
lastContent: this.lastContentRender(message),
lastSendTime: message.sendTime
});
@@ -180,26 +236,36 @@ export default {
);
},
_emitPullMessages(next) {
this._changeContactLock = true;
this.$emit(
"pull-messages",
this.currentContact,
(messages, isEnd = false) => {
this._addMessage(messages, this.currentContactId, 0);
CacheMessageLoaded.set(this.currentContactId, isEnd);
this.CacheMessageLoaded.set(this.currentContactId, isEnd);
if (isEnd == true) this.$refs.messages.loaded();
this.updateCurrentMessages();
this._changeContactLock = false;
next(isEnd);
}
},
this
);
},
clearCacheContainer(name) {
CacheContactContainer.remove(name);
CacheMenuContainer.remove(name);
this.CacheContactContainer.remove(name);
this.CacheMenuContainer.remove(name);
},
_renderWrapper(children) {
return (
<div
style={{
width: this.width,
height: this.height
}}
class={[
"lemon-wrapper",
`lemon-wrapper--theme-${this.theme}`,
{ "lemon-wrapper--simple": this.simple },
this.drawerVisible && "lemon-wrapper--drawer-show"
]}
>
@@ -245,7 +311,7 @@ export default {
{ "lemon-menu__item--active": this.activeSidebar == name }
]}
on-click={() => {
fastDone(click, () => {
funCall(click, () => {
if (name) this.changeMenu(name);
});
}}
@@ -264,51 +330,56 @@ export default {
_renderSidebarMessage() {
return this._renderSidebar(
[
useScopedSlot(this.$scopedSlots["message-sidebar"]),
useScopedSlot(this.$scopedSlots["sidebar-message-top"], null, this),
this.lastMessages.map(contact => {
return this._renderContact(
{
contact,
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 {
click: customClick,
renderContainer,
id: contactId
} = props.contact;
const click = () => {
fastDone(customClick, () => {
funCall(customClick, () => {
onClick();
this._customContainerReady(
renderContainer,
CacheContactContainer,
this.CacheContactContainer,
contactId
);
});
};
return (
<lemon-contact
class={{
"lemon-contact--active": this.currentContactId == props.contact.id
}}
v-lemon-contextmenu_contact={this.contactContextmenu}
props={props}
on-click={click}
/>
scopedSlots={{ default: slot }}
></lemon-contact>
);
},
_renderSidebarContact() {
let prevIndex;
return this._renderSidebar(
[
useScopedSlot(this.$scopedSlots["contact-sidebar"]),
useScopedSlot(this.$scopedSlots["sidebar-contact-top"], null, this),
this.contacts.map(contact => {
if (!contact.index) return;
contact.index = contact.index.replace(/\[[0-9]*\]/, "");
@@ -321,20 +392,29 @@ export default {
contact: contact,
simple: true
},
() => this.changeContact(contact.id)
() => {
this.changeContact(contact.id);
},
this.$scopedSlots["sidebar-contact"]
)
];
prevIndex = contact.index;
return node;
})
],
DEFAULT_MENU_CONTACTS
DEFAULT_MENU_CONTACTS,
useScopedSlot(this.$scopedSlots["sidebar-contact-fixedtop"], null, this)
);
},
_renderSidebar(children, name) {
_renderSidebar(children, name, fixedtop) {
return (
<div class="lemon-sidebar" v-show={this.activeSidebar == name}>
{children}
<div
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>
);
},
@@ -356,22 +436,22 @@ export default {
const cls = "lemon-container";
const curact = this.currentContact;
let defIsShow = true;
for (const name in CacheContactContainer.get()) {
for (const name in this.CacheContactContainer.get()) {
const show = curact.id == name && this.currentIsDefSidebar;
defIsShow = !show;
nodes.push(
<div class={cls} v-show={show}>
{CacheContactContainer.get(name)}
{this.CacheContactContainer.get(name)}
</div>
);
}
for (const name in CacheMenuContainer.get()) {
for (const name in this.CacheMenuContainer.get()) {
nodes.push(
<div
class={cls}
v-show={this.activeSidebar == name && !this.currentIsDefSidebar}
>
{CacheMenuContainer.get(name)}
{this.CacheMenuContainer.get(name)}
</div>
);
}
@@ -384,7 +464,7 @@ export default {
<div class="lemon-container__title">
<div class="lemon-container__displayname">
{useScopedSlot(
this.$scopedSlots["contact-title"],
this.$scopedSlots["message-title"],
curact.displayName,
curact
)}
@@ -392,6 +472,8 @@ export default {
</div>
<lemon-messages
ref="messages"
hide-time={this.hideMessageTime}
hide-name={this.hideMessageName}
time-format={this.messageTimeFormat}
reverse-user-id={this.user.id}
on-reach-top={this._emitPullMessages}
@@ -399,6 +481,9 @@ export default {
/>
<lemon-editor
ref="editor"
tools={this.editorTools}
sendText={this.sendText}
sendKey={this.sendKey}
onSend={this._handleSend}
onUpload={this._handleUpload}
/>
@@ -421,6 +506,12 @@ export default {
<h4>{curact.displayName}</h4>
<lemon-button
on-click={() => {
if (isEmpty(curact.lastContent)) {
this.updateContact({
id: curact.id,
lastContent: " "
});
}
this.changeContact(curact.id, DEFAULT_MENU_LASTMESSAGES);
}}
>
@@ -433,12 +524,14 @@ export default {
);
return nodes;
},
_handleSidebarScroll() {
contextmenu.hide();
},
_addContact(data, t) {
const type = {
0: "unshift",
1: "push"
}[t];
//this.contacts[type](cloneDeep(data));
this.contacts[type](data);
},
_addMessage(data, contactId, t) {
@@ -447,10 +540,8 @@ export default {
1: "push"
}[t];
if (!Array.isArray(data)) data = [data];
messages[contactId] = messages[contactId] || [];
messages[contactId][type](...data);
//console.log(messages[contactId]);
this.forceUpdateMessage();
allMessages[contactId] = allMessages[contactId] || [];
allMessages[contactId][type](...data);
},
/**
* 设置最新消息DOM
@@ -461,6 +552,12 @@ export default {
lastContentRender[messageType] = render;
},
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);
},
/**
@@ -468,14 +565,22 @@ export default {
* @param {String} str 被替换的字符串
* @return {String} 替换后的字符串
*/
replaceEmojiName(str) {
emojiNameToImage(str) {
return str.replace(/\[!(\w+)\]/gi, (str, match) => {
const file = match;
return emojiMap[file]
? `<img src="${emojiMap[file]}" />`
? `<img emoji-name="${match}" src="${emojiMap[file]}" />`
: `[!${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
*/
changeContact(contactId, menuName) {
if (this.currentContactId == contactId) {
this.currentContactId = undefined;
}
async changeContact(contactId, menuName) {
if (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.$emit("change-contact", this.currentContact);
if (!this.currentContactId) return false;
this.$emit("change-contact", this.currentContact, this);
if (isFunction(this.currentContact.renderContainer)) {
return;
}
if (this._menuIsMessages()) {
if (!CacheMessageLoaded.has(contactId)) {
this.$refs.messages.resetLoadState();
}
if (!messages[contactId]) {
this._emitPullMessages(isEnd => this.messageViewToBottom());
} else {
setTimeout(() => {
this.messageViewToBottom();
}, 0);
}
//填充草稿内容
const draft = this.CacheDraft.get(contactId) || "";
if (draft) this.setEditorValue(draft);
if (this.CacheMessageLoaded.has(contactId)) {
this.$refs.messages.loaded();
} else {
this.$refs.messages.resetLoadState();
}
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 contactId 联系人 id
*/
removeMessage(messageId, contactId) {
const index = this.findMessageIndexById(messageId, contactId);
if (index !== -1) {
messages[contactId].splice(index, 1);
this.forceUpdateMessage();
}
removeMessage(messageId) {
const message = this.findMessage(messageId);
if (!message) return false;
const index = allMessages[message.toContactId].findIndex(
({ id }) => id == messageId
);
allMessages[message.toContactId].splice(index, 1);
return true;
},
/**
* 修改聊天一条聊天消息
* @param {Message} data 根据 data.id 查找聊天消息并覆盖传入的值
* @param contactId 联系人 id
*/
updateMessage(messageId, contactId, data) {
const index = this.findMessageIndexById(messageId, contactId);
if (index !== -1) {
messages[contactId][index] = Object.assign(
messages[contactId][index],
data
);
console.log("--------", messages[contactId][index]);
this.forceUpdateMessage(messageId);
}
updateMessage(message) {
if (!message.id) return false;
let historyMessage = this.findMessage(message.id);
if (!historyMessage) return false;
historyMessage = Object.assign(historyMessage, message, {
toContactId: historyMessage.toContactId
});
return true;
},
/**
* 手动更新对话消息
@@ -567,6 +696,7 @@ export default {
* @param {String} name 按钮 name
*/
changeMenu(name) {
if (this._changeContactLock) return false;
this.$emit("change-menu", name);
this.activeSidebar = name;
},
@@ -577,11 +707,20 @@ export default {
* EmojiItem = {name: wx,title: 微笑,src: url} 无分组
*/
initEmoji(data) {
let flatData = [];
this.$refs.editor.initEmoji(data);
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 = [];
if (Array.isArray(data)) {
const indexMap = {
lastMessages: 0,
messages: 0,
contacts: 1
};
const indexKeys = Object.keys(indexMap);
@@ -629,7 +768,7 @@ export default {
if (item.renderContainer) {
this._customContainerReady(
item.renderContainer,
CacheMenuContainer,
this.CacheMenuContainer,
item.name
);
}
@@ -647,7 +786,7 @@ export default {
* @param {Array<Contact>} data 联系人列表
*/
initContacts(data) {
this.contacts.push(...data);
this.contacts = data;
this.sortContacts();
},
/**
@@ -659,13 +798,43 @@ export default {
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.toContactId;
const index = this.findContactIndexById(contactId);
if (index !== -1) {
@@ -690,16 +859,22 @@ export default {
findContactIndexById(contactId) {
return this.contacts.findIndex(item => item.id == contactId);
},
findMessageIndexById(messageId, contactId) {
const msg = messages[contactId];
if (isEmpty(msg)) {
return -1;
}
return msg.findIndex(item => item.id == messageId);
/**
* 根据 id 查找判断是否存在联系人
* @param contactId 联系人 id
* @return {Boolean}
*/
hasContact(contactId) {
return this.findContactIndexById(contactId) !== -1;
},
findMessageById(messageId, contactId) {
const index = this.findMessageIndexById(messageId, contactId);
if (index !== -1) return messages[contactId][index];
findMessage(messageId) {
for (const key in allMessages) {
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() {
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>}
*/
getMessages(contactId) {
return (contactId ? messages[contactId] : messages) || [];
},
// 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;
return (contactId ? allMessages[contactId] : allMessages) || [];
},
changeDrawer(render) {
this.drawerVisible = !this.drawerVisible;
@@ -754,16 +919,14 @@ export default {
};
</script>
<style lang="stylus">
wrapper-width = 850px
drawer-width = 200px
bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
@import '~styles/utils/index'
+b(lemon-wrapper)
width wrapper-width
height 580px
display flex
font-size 14px
font-family "Microsoft YaHei"
//mask-image radial-gradient(circle, white 100%, black 100%)
background #efefef
transition all .4s bezier
@@ -812,13 +975,18 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
+b(lemon-sidebar)
width 250px
background #efefef
overflow-y auto
scrollbar-light()
display flex
flex-direction column
+e(scroll){
overflow-y auto
scrollbar-light()
}
+e(label)
padding 6px 14px 6px 14px
color #666
font-size 12px
margin 0
text-align left
+b(lemon-contact--active)
background #d9d9d9
+b(lemon-container)
@@ -827,7 +995,7 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
background #f4f4f4
word-break()
position relative
z-index 2
z-index 10
+e(title)
padding 15px 15px
+e(displayname)
@@ -842,11 +1010,10 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
overflow hidden
background #f4f4f4
transition width .4s bezier
z-index 1
z-index 9
width drawer-width
height 100%
box-sizing border-box
//border-left 1px solid #e9e9e9
+b(lemon-wrapper)
+m(drawer-show)
+b(lemon-drawer)
@@ -861,4 +1028,77 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
font-weight normal
margin 10px 0 20px 0
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>
+95 -50
View File
@@ -1,8 +1,17 @@
<script>
import { useScopedSlot } from "utils";
export default {
name: "lemonMessageBasic",
inject: ["IMUI"],
inject: {
IMUI: {
from: "IMUI",
default() {
return this;
}
}
},
props: {
contextmenu: Array,
message: {
type: Object,
default: () => {
@@ -14,7 +23,8 @@ export default {
default: () => ""
},
reverse: Boolean,
hiddenTitle: Boolean
hideName: Boolean,
hideTime: Boolean
},
data() {
return {};
@@ -25,9 +35,10 @@ export default {
<div
class={[
"lemon-message",
`lemon-message--status-${status}`,
{
"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}
</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
class="lemon-message__content"
on-click={e => {
this._emitClick(e, "content");
}}
>
{this.useScopedSlots("content", this.message)}
</div>
<div
class="lemon-message__status"
on-click={e => {
this._emitClick(e, "status");
}}
>
{this._renderStatue(status)}
<div class="lemon-message__content-flex">
<div
v-lemon-contextmenu_message={this.IMUI.contextmenu}
class="lemon-message__content"
on-click={e => {
this._emitClick(e, "content");
}}
>
{useScopedSlot(this.$scopedSlots["content"], null, this.message)}
</div>
<div class="lemon-message__content-after">
{useScopedSlot(
this.IMUI.$scopedSlots["message-after"],
null,
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>
@@ -78,29 +116,7 @@ export default {
watch: {},
methods: {
_emitClick(e, key) {
this.IMUI.$emit("message-click", e, key, this.message);
},
_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;
this.IMUI.$emit("message-click", e, key, this.message, this.IMUI);
}
}
};
@@ -118,8 +134,8 @@ arrow()
display flex
padding 10px 0
+e(time)
color #bbb
padding 0 4px
color #b9b9b9
padding 0 5px
+e(inner)
position relative
+e(avatar)
@@ -133,7 +149,9 @@ arrow()
line-height 14px
padding-bottom 6px
user-select none
color #999
color #666
+e(content-flex)
display flex
+e(content)
font-size 14px
line-height 20px
@@ -141,7 +159,7 @@ arrow()
background #fff
border-radius 4px
position relative
margin 0 46px 0 0
margin 0
img
video
background #e9e9e9
@@ -151,22 +169,49 @@ arrow()
left -4px
border-left none
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)
position absolute
top 23px
right 20px
color #aaa
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)
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)
flex-direction row-reverse
+e(status)
left 20px
left 26px
right auto
+e(content)
background #35d863
margin 0 0 0 46px
&:before
arrow()
left auto
@@ -178,9 +223,9 @@ arrow()
+e(avatar)
padding-right 0
padding-left 10px
+m(hidden-title)
+m(hide-name)
+e(status)
top 7px
top 3px
+e(title)
display none
+e(content)
+12 -1
View File
@@ -2,13 +2,24 @@
export default {
name: "lemonMessageEvent",
inheritAttrs: false,
inject: ["IMUI"],
render() {
const { content } = this.$attrs.message;
return (
<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>
);
},
methods: {
_emitClick(e, key) {
this.IMUI.$emit("message-click", e, key, this.$attrs.message, this.IMUI);
}
}
};
</script>
+3 -1
View File
@@ -10,7 +10,7 @@ export default {
props={{ ...this.$attrs }}
scopedSlots={{
content: props => {
const content = this.IMUI.replaceEmojiName(props.content);
const content = this.IMUI.emojiNameToImage(props.content);
return <span domProps={{ innerHTML: content }} />;
}
}}
@@ -29,6 +29,8 @@ export default {
height 18px
display inline-block
background transparent
position relative
top -1px
padding 0 2px
vertical-align middle
</style>
+22 -13
View File
@@ -1,9 +1,14 @@
<script>
import { hoursTimeFormat } from "utils";
import contextmenu from "../directives/contextmenu";
export default {
name: "LemonMessages",
components: {},
props: {
//是否隐藏消息发送人昵称
hideName: Boolean,
//是否隐藏显示消息时间
hideTime: Boolean,
reverseUserId: String,
timeRange: {
type: Number,
@@ -52,24 +57,26 @@ export default {
message: {
id: "__time__",
type: "event",
content: this.timeFormat(message.sendTime)
content: hoursTimeFormat(message.sendTime)
}
}}
/>
);
}
node.push(
<tagName
ref="message"
refInFor={true}
attrs={{
timeFormat: this.msecRange > 0 ? () => {} : this.timeFormat,
message: message,
reverse: this.reverseUserId == message.fromUser.id,
hiddenTitle: false
}}
/>
);
let attrs;
if (message.type == "event") {
attrs = { message: message };
} else {
attrs = {
timeFormat: this.timeFormat,
message: message,
reverse: this.reverseUserId == message.fromUser.id,
hideTime: this.hideTime,
hideName: this.hideName
};
}
node.push(<tagName ref="message" refInFor={true} attrs={attrs} />);
return node;
})}
</div>
@@ -90,6 +97,7 @@ export default {
},
loaded() {
this._loadend = true;
this.$forceUpdate();
},
resetLoadState() {
this._loading = false;
@@ -97,6 +105,7 @@ export default {
},
async _handleScroll(e) {
const { target } = e;
contextmenu.hide();
if (
target.scrollTop == 0 &&
this._loading == false &&
+11 -8
View File
@@ -1,6 +1,6 @@
<script>
const popoverCloseQueue = [];
const popoverCloseAll = () => popoverCloseQueue.forEach(callback => callback());
import contextmenu from "../directives/contextmenu";
const triggerEvents = {
hover(el) {},
focus(el) {
@@ -14,6 +14,7 @@ const triggerEvents = {
click(el) {
el.addEventListener("click", e => {
e.stopPropagation();
contextmenu.hide();
this.changeVisible();
});
},
@@ -51,7 +52,7 @@ export default {
render() {
return (
<span style="position:relative">
<transition name="slide-top">
<transition name="lemon-slide-top">
{this.visible && (
<div
class="lemon-popover"
@@ -59,7 +60,6 @@ export default {
style={this.popoverStyle}
on-click={e => e.stopPropagation()}
>
<div class="lemon-popover__title" />
<div class="lemon-popover__content">{this.$slots.content}</div>
<div class="lemon-popover__arrow" />
</div>
@@ -96,9 +96,12 @@ export default {
this.visible ? this.close() : this.open();
},
open() {
popoverCloseAll();
this.closeAll();
this.visible = true;
},
closeAll() {
popoverCloseQueue.forEach(callback => callback());
},
close() {
this.visible = false;
}
@@ -117,7 +120,7 @@ export default {
z-index 10
background-color #fff
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
transform-origin 50% 150%
+e(content)
@@ -135,9 +138,9 @@ export default {
width 8px
height 8px
background #fff
.slide-top-leave-active ,.slide-top-enter-active
transition all .3s cubic-bezier(0.645, 0.045, 0.355, 1)
.slide-top-enter, .slide-top-leave-to
.lemon-slide-top-leave-active ,.lemon-slide-top-enter-active
transition all .2s cubic-bezier(0.645, 0.045, 0.355, 1)
.lemon-slide-top-enter, .lemon-slide-top-leave-to
transform translateY(-10px) scale(.8)
opacity 0
</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 LemonPopover from "./components/popover";
import LemonButton from "./components/button";
@@ -14,7 +15,7 @@ import lemonMessageEvent from "./components/message/event";
import LemonIMUI from "./components/index";
import "./styles/common/index.styl";
const version = "0.1";
const version = "1.4.2";
const components = [
LemonIMUI,
LemonContact,
@@ -32,6 +33,7 @@ const components = [
lemonMessageEvent
];
const install = (Vue, opts = {}) => {
Vue.directive("LemonContextmenu", Contextmenu);
components.forEach(component => {
Vue.component(component.name, component);
});
+6 -8
View File
@@ -1,17 +1,15 @@
import {clearHtml } from 'utils';
export default {
voice(message) {
return "[语音]";
},
file(message) {
return "[文件]";
},
video(message) {
return "[视频]";
},
image(message) {
return "[图片]";
},
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-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');
src:url('../fonts/icon.woff') format('woff');
}
[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];
}
}
// 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 DEFAULT_MENU_LASTMESSAGES = "lastMessages";
export const DEFAULT_MENU_LASTMESSAGES = "messages";
export const DEFAULT_MENU_CONTACTS = "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;
}
export function fastDone(event, callback) {
export function funCall(event, callback) {
if (isFunction(event)) {
event(() => {
callback();
@@ -70,7 +70,14 @@ export function fastDone(event, callback) {
export function arrayIntersect(a, b) {
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) {
throw new Error(text);
}
@@ -96,9 +103,6 @@ export function mergeDeep(o1, o2) {
return o1;
}
export function toEmojiName(str) {
return str.replace(/<img emoji-name=\"([^\"]*?)\" [^>]*>/gi, "[!$1]");
}
export function formatByte(value) {
if (null == value || value == "") {
return "0 Bytes";