Commit 342401e1 by panjiangyi

update dependency

parent 84b48c2e
.idea/*
.nyc_output
build
node_modules
test
coverage
*.log
yarn.lock
.DS_Store
\ No newline at end of file
......@@ -67,23 +67,23 @@
</template>
<script lang="ts">
import { Component, Vue, Ref } from "vue-property-decorator"
import { formatFileSize } from "../utils"
import { Component, Ref, Vue, Watch } from "vue-property-decorator";
import { namespace } from "vuex-class";
import {
getFileType,
getSvg,
MAX_IMAGE_SIZE_STRING,
MAX_FILE_SIZE,
MAX_FILE_SIZE_STRING,
MESSAGE_FILE_EMPTY,
MAX_IMAGE_SIZE,
MESSAGE_IMAGE_TOO_LARGE,
MAX_FILE_SIZE,
MAX_IMAGE_SIZE_STRING,
MESSAGE_FILE_EMPTY,
MESSAGE_FILE_TOO_LARGE,
} from "../components/file-controller"
import { ChatStore } from "../store/model"
import { namespace } from "vuex-class"
import { Watch } from "vue-property-decorator"
import { EmojiService } from "../service/emoji"
MESSAGE_IMAGE_TOO_LARGE,
} from "../components/file-controller";
import { EmojiService } from "../service/emoji";
import { ChatStore } from "../store/model";
import { formatFileSize } from "../utils";
export const enum InputMessageType {
Text = "text",
......@@ -92,32 +92,32 @@ export const enum InputMessageType {
}
export interface InputMessageBody {
text?: string
url?: string
name?: string
size?: number
text?: string;
url?: string;
name?: string;
size?: number;
}
export interface InputMessage {
type: InputMessageType
body: InputMessageBody
file?: File | null
type: InputMessageType;
body: InputMessageBody;
file?: File | null;
}
const chatStoreNamespace = namespace("chatStore")
const chatStoreNamespace = namespace("chatStore");
const chatCache: { [key: number]: any } = {}
const chatCache: { [key: number]: any } = {};
export const IMAGE_INFO_CLASS = "img-info"
export const FILE_INFO_CLASS = "file-info"
export const IMAGE_INFO_CLASS = "img-info";
export const FILE_INFO_CLASS = "file-info";
export function isImageOrFile(node: ChildNode) {
const e = node as HTMLElement
const e = node as HTMLElement;
return (
e.classList &&
(e.classList.contains(IMAGE_INFO_CLASS) ||
e.classList.contains(FILE_INFO_CLASS))
)
);
}
@Component({ components: {} })
......@@ -140,23 +140,23 @@ export default class Input extends Vue {
@Watch("chatId")
private onChatIdChanged(v: number, old: number) {
if (old) {
const current = this.getNodeListFromInputBox()
const current = this.getNodeListFromInputBox();
if (current && current.length) {
chatCache[old] = current
chatCache[old] = current;
}
}
this.clearInput()
this.clearInput();
if (v) {
const cache = chatCache[v]
const cache = chatCache[v];
if (cache) {
const e = document.querySelector(
"#chat-input-box"
) as HTMLElement
) as HTMLElement;
if (e) {
for (const node of cache as ChildNode[]) {
e.appendChild(node)
e.appendChild(node);
}
}
}
......@@ -164,40 +164,40 @@ export default class Input extends Vue {
}
public mounted() {
this.messageInputBox.addEventListener("paste", this.handlePasteEvent)
document.addEventListener("click", this.hideEmoji)
document.addEventListener("selectionchange", this.handleSaveRange)
this.messageInputBox.addEventListener("paste", this.handlePasteEvent);
document.addEventListener("click", this.hideEmoji);
document.addEventListener("selectionchange", this.handleSaveRange);
this.$onBeforeDestroy(() => {
document.removeEventListener("click", this.hideEmoji)
document.removeEventListener("click", this.hideEmoji);
document.removeEventListener(
"selectionchange",
this.handleSaveRange
)
})
this.setupEmoji()
this.focus()
);
});
this.setupEmoji();
this.focus();
}
public focus() {
this.messageInputBox.focus()
this.messageInputBox.focus();
}
private clearInput() {
this.messageInputBox.innerHTML = ""
this.messageInputBox.innerHTML = "";
const input = document.getElementById(
"chat-upload-file"
) as HTMLInputElement
) as HTMLInputElement;
if (input) {
input.value = ""
input.value = "";
}
}
private allowLoadImg() {
this.acceptType = "image/*"
this.acceptType = "image/*";
}
private allowLoadFile() {
this.acceptType = "*"
this.acceptType = "*";
}
private async handlePasteEvent(event: ClipboardEvent) {
......@@ -206,39 +206,39 @@ export default class Input extends Vue {
* 1、浏览器自带复制粘贴图片到输入框的功能,与js加工后的图片重复了,
* 2、默认复制粘贴功能会粘贴dom结构
* */
event.preventDefault()
const items = event.clipboardData && event.clipboardData.items
let html = ""
const promiseArr = []
event.preventDefault();
const items = event.clipboardData && event.clipboardData.items;
let html = "";
const promiseArr = [];
if (items && items.length) {
// 检索剪切板items中类型带有image的
for (let i = 0; i < items.length; i++) {
if (items[i].kind === "file") {
const file = items[i].getAsFile()
const file = items[i].getAsFile();
if (file) {
if (file.size <= 0) {
this.$emit("error", MESSAGE_FILE_EMPTY)
return
this.$emit("error", MESSAGE_FILE_EMPTY);
return;
}
if (this.isImage(file)) {
if (file.size >= MAX_IMAGE_SIZE) {
this.$emit("error", MESSAGE_IMAGE_TOO_LARGE)
return
this.$emit("error", MESSAGE_IMAGE_TOO_LARGE);
return;
}
html += this.buildImageHtml(file)
html += this.buildImageHtml(file);
} else {
if (file.size >= MAX_FILE_SIZE) {
this.$emit("error", MESSAGE_FILE_TOO_LARGE)
return
this.$emit("error", MESSAGE_FILE_TOO_LARGE);
return;
}
html += this.buildFileHtml(file)
html += this.buildFileHtml(file);
}
break
break;
}
} else {
promiseArr.push(
new Promise<void>((done) => {
const contentType = items[i].type
new Promise<void>((reslove) => {
const contentType = items[i].type;
items[i].getAsString((k) => {
/*
* items第一项是文本
......@@ -247,56 +247,56 @@ export default class Input extends Vue {
*/
if (i === 0) {
if (contentType === "text/plain") {
html += k
html += k;
}
} else if (i === 1) {
const srcRegex =
/<img[^>]+src="([^">]+)"\s+title="([^">]+)"/g
let result
/<img[^>]+src="([^">]+)"\s+title="([^">]+)"/g;
let result;
do {
result = srcRegex.exec(k)
result = srcRegex.exec(k);
if (result) {
const [, src, name] = result
const [, src, name] = result;
html += `<img tabindex="-1" src="${src}" data-image='${JSON.stringify(
{
url: src,
name,
}
)}'>`
html = html.replace(name, "")
)}'>`;
html = html.replace(name, "");
}
} while (result)
} while (result);
}
done()
})
reslove();
});
})
)
);
}
}
}
await Promise.all(promiseArr)
await Promise.all(promiseArr);
if (html) {
this.insertHtmlAtCaret(html)
this.insertHtmlAtCaret(html);
}
}
private async handleSendMsg(e: Event) {
// 防止换行
e.preventDefault()
e.preventDefault();
return new Promise((resolve, reject) => {
try {
const data = this.getNodeListFromInputBox()
this.$emit("send", data, resolve)
const data = this.getNodeListFromInputBox();
this.$emit("send", data, resolve);
} catch (e) {
this.$emit("error", e)
reject(e)
this.$emit("error", e);
reject(e);
}
}).then(() => {
this.clearInput()
this.clearInput();
if (this.chatId) {
chatCache[this.chatId] = []
chatCache[this.chatId] = [];
}
})
});
}
/**
......@@ -304,57 +304,57 @@ export default class Input extends Vue {
* @returns 返回的是节点数组
*/
private getNodeListFromInputBox() {
this.messageInputBox.normalize()
const nodes = Array.from(this.messageInputBox.childNodes)
return this.combine(nodes)
this.messageInputBox.normalize();
const nodes = Array.from(this.messageInputBox.childNodes);
return this.combine(nodes);
}
/**
* 文本,链接等需要合并成纯文本发送
*/
private combine(nodes: ChildNode[]) {
const sendingNodes: ChildNode[] = []
let needCreateNewNode = false
let text = ""
const sendingNodes: ChildNode[] = [];
let needCreateNewNode = false;
let text = "";
for (const item of nodes) {
if (!isImageOrFile(item) && item.textContent) {
if (needCreateNewNode) {
text = ""
needCreateNewNode = false
text = "";
needCreateNewNode = false;
}
text += item.textContent
text += item.textContent;
} else {
needCreateNewNode = true
needCreateNewNode = true;
if (text) {
this.checkTextLength(text)
const node = document.createTextNode(text)
sendingNodes.push(node)
this.checkTextLength(text);
const node = document.createTextNode(text);
sendingNodes.push(node);
}
sendingNodes.push(item)
sendingNodes.push(item);
}
}
if (text) {
this.checkTextLength(text)
const node = document.createTextNode(text)
sendingNodes.push(node)
this.checkTextLength(text);
const node = document.createTextNode(text);
sendingNodes.push(node);
}
return sendingNodes
return sendingNodes;
}
private checkTextLength(text: string) {
if (text.length >= 4000) {
throw new Error("消息不能超过4000个字")
throw new Error("消息不能超过4000个字");
}
}
private handleSaveRange() {
const sel = window.getSelection()
const sel = window.getSelection();
if (sel && sel.rangeCount) {
const range = sel.getRangeAt(0)
const oldRange = this.getRange && this.getRange()
const range = sel.getRangeAt(0);
const oldRange = this.getRange && this.getRange();
if (
this.messageInputBox &&
this.messageInputBox.contains(range.endContainer)
......@@ -365,10 +365,10 @@ export default class Input extends Vue {
range.endContainer === oldRange.endContainer &&
range.endOffset === oldRange.endOffset
) {
return
return;
}
this.saveRange && this.saveRange(range)
this.saveRange && this.saveRange(range);
}
}
}
......@@ -380,74 +380,74 @@ export default class Input extends Vue {
* 在光标处插入元素
*/
public insertHtmlAtCaret = (function () {
let range: Range | null = null
let range: Range | null = null;
return function (this: Input, html: string) {
if (this.saveRange == null) {
this.saveRange = (alternate) => (range = alternate)
this.saveRange = (alternate) => (range = alternate);
}
if (this.getRange == null) {
this.getRange = () => range
this.getRange = () => range;
}
const sel = window.getSelection()
const sel = window.getSelection();
if (sel) {
if (range) {
range.deleteContents()
sel.removeAllRanges()
sel.addRange(range)
range.deleteContents();
sel.removeAllRanges();
sel.addRange(range);
} else {
this.focus()
range = sel.getRangeAt(0)
document.execCommand("selectAll")
window.getSelection()?.collapseToEnd()
this.focus();
range = sel.getRangeAt(0);
document.execCommand("selectAll");
window.getSelection()?.collapseToEnd();
}
}
const el = document.createElement("div")
el.innerHTML = html
const frag = document.createDocumentFragment()
let node = null
let lastNode = null
const el = document.createElement("div");
el.innerHTML = html;
const frag = document.createDocumentFragment();
let node = null;
let lastNode = null;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node)
lastNode = frag.appendChild(node);
}
range?.insertNode(frag)
range?.insertNode(frag);
// Preserve the selection
if (lastNode) {
if (range) {
range = range.cloneRange()
range = range.cloneRange();
range.setStartAfter(lastNode)
range.collapse(true)
sel?.removeAllRanges()
sel?.addRange(range)
range.setStartAfter(lastNode);
range.collapse(true);
sel?.removeAllRanges();
sel?.addRange(range);
}
}
}
};
})()
private selectEmoji(emoji: any) {
this.insertHtmlAtCaret(emoji.emoji_chars)
this.hideEmoji()
this.insertHtmlAtCaret(emoji.emoji_chars);
this.hideEmoji();
}
private isImage(file: File) {
return file.type.startsWith("image")
return file.type.startsWith("image");
}
private buildImageHtml(file: File) {
const url = URL.createObjectURL(file)
const url = URL.createObjectURL(file);
return `<img class="${IMAGE_INFO_CLASS}" tabindex="-1" src="${url}" data-image='${JSON.stringify(
{
url,
name: file.name,
size: file.size,
}
)}'>`
)}'>`;
}
private buildFileHtml(file: File) {
const extension = file.name.split(".")
const type = getFileType(file.name)
const extension = file.name.split(".");
const type = getFileType(file.name);
return `<div class="${FILE_INFO_CLASS}" tabindex="-1" title="${
extension[extension.length - 1]
}" data-file='${JSON.stringify({
......@@ -460,73 +460,73 @@ export default class Input extends Vue {
file.size
)}</div></div><span class="file-icon" title="${type}">${getSvg(
type
)}</span></div> ` // 注意</div>最后面有个空
)}</span></div> `; // 注意</div>最后面有个空
}
private onChange(e: Event) {
const target = e.target as HTMLElement
const target = e.target as HTMLElement;
if (target) {
const input = document.getElementById(
target.id as string
) as HTMLInputElement
) as HTMLInputElement;
if (input) {
const files = input.files
const files = input.files;
if (files && files.length) {
let html = ""
let html = "";
for (let index = 0; index < files.length; index++) {
const file = files[index]
const file = files[index];
if (file.size <= 0) {
this.$emit("error", MESSAGE_FILE_EMPTY)
return
this.$emit("error", MESSAGE_FILE_EMPTY);
return;
}
if (this.isImage(file)) {
if (file.size >= MAX_IMAGE_SIZE) {
this.$emit("error", MESSAGE_IMAGE_TOO_LARGE)
return
this.$emit("error", MESSAGE_IMAGE_TOO_LARGE);
return;
}
html += this.buildImageHtml(file)
html += this.buildImageHtml(file);
} else {
if (file.size >= MAX_FILE_SIZE) {
this.$emit("error", MESSAGE_FILE_TOO_LARGE)
return
this.$emit("error", MESSAGE_FILE_TOO_LARGE);
return;
}
html += this.buildFileHtml(file)
html += this.buildFileHtml(file);
}
}
this.insertHtmlAtCaret(html)
this.insertHtmlAtCaret(html);
}
}
}
}
private toggleEmoji() {
this.emojiPanelVisibility = !this.emojiPanelVisibility
this.emojiPanelVisibility = !this.emojiPanelVisibility;
}
private hideEmoji(e?: Event) {
if (e && e.target) {
const target = e.target as HTMLElement
const target = e.target as HTMLElement;
if (target.closest(".emoji-picker")) {
return
return;
}
}
this.emojiPanelVisibility = false
this.emojiPanelVisibility = false;
}
private noop() {
return
}
private setupEmoji() {
EmojiService.onReady(() => {
const service = new EmojiService()
const service = new EmojiService();
service.getEmoji().then((r) => {
if (r) {
this.emoji = r.list
this.emoji = r.list;
}
})
})
});
});
}
}
</script>
......
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"@types/node": {
"version": "16.3.1",
"resolved": "http://npm.job.qinqinxiaobao.com/@types%2fnode/-/node-16.3.1.tgz",
"integrity": "sha1-JGkforDD7IwNNL/P1JXtrFWT67Q="
},
"@types/sha1": {
"version": "1.1.3",
"resolved": "http://npm.job.qinqinxiaobao.com/@types%2fsha1/-/sha1-1.1.3.tgz",
"integrity": "sha1-1Mw38X0JPFJDgtkDJrpydO9GN6g=",
"requires": {
"@types/node": "*"
}
},
"axios": {
"version": "0.19.2",
"resolved": "http://npm.job.qinqinxiaobao.com/axios/-/axios-0.19.2.tgz",
"integrity": "sha1-PqNsXYgY0NX4qKl6bTa4bNwAyyc=",
"requires": {
"follow-redirects": "1.5.10"
}
},
"call-bind": {
"version": "1.0.2",
"resolved": "http://npm.job.qinqinxiaobao.com/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha1-sdTonmiBGcPJqQOtMKuy9qkZvjw=",
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
}
},
"charenc": {
"version": "0.0.2",
"resolved": "http://npm.job.qinqinxiaobao.com/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
},
"crypt": {
"version": "0.0.2",
"resolved": "http://npm.job.qinqinxiaobao.com/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
},
"debug": {
"version": "3.1.0",
"resolved": "http://npm.job.qinqinxiaobao.com/debug/-/debug-3.1.0.tgz",
"integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
"requires": {
"ms": "2.0.0"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "http://npm.job.qinqinxiaobao.com/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha1-e3qfmuov3/NnhqlP9kPtB/T/Xio=",
"requires": {
"debug": "=3.1.0"
}
},
"function-bind": {
"version": "1.1.1",
"resolved": "http://npm.job.qinqinxiaobao.com/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0="
},
"get-intrinsic": {
"version": "1.1.1",
"resolved": "http://npm.job.qinqinxiaobao.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha1-FfWfN2+FXERpY5SPDSTNNje0q8Y=",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
}
},
"has": {
"version": "1.0.3",
"resolved": "http://npm.job.qinqinxiaobao.com/has/-/has-1.0.3.tgz",
"integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-symbols": {
"version": "1.0.2",
"resolved": "http://npm.job.qinqinxiaobao.com/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha1-Fl0wcMADCXUqEjakeTMeOsVvFCM="
},
"ms": {
"version": "2.0.0",
"resolved": "http://npm.job.qinqinxiaobao.com/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"object-inspect": {
"version": "1.11.0",
"resolved": "http://npm.job.qinqinxiaobao.com/object-inspect/-/object-inspect-1.11.0.tgz",
"integrity": "sha1-nc6xRs7dQUig2eUauI00z1CZIrE="
},
"qs": {
"version": "6.10.1",
"resolved": "http://npm.job.qinqinxiaobao.com/qs/-/qs-6.10.1.tgz",
"integrity": "sha1-STFIL6jWR6Wqt5nFJx0hM7mB+2o=",
"requires": {
"side-channel": "^1.0.4"
}
},
"sha1": {
"version": "1.1.1",
"resolved": "http://npm.job.qinqinxiaobao.com/sha1/-/sha1-1.1.1.tgz",
"integrity": "sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg=",
"requires": {
"charenc": ">= 0.0.1",
"crypt": ">= 0.0.1"
}
},
"side-channel": {
"version": "1.0.4",
"resolved": "http://npm.job.qinqinxiaobao.com/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha1-785cj9wQTudRslxY1CkAEfpeos8=",
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
}
},
"tweetnacl": {
"version": "1.0.3",
"resolved": "http://npm.job.qinqinxiaobao.com/tweetnacl/-/tweetnacl-1.0.3.tgz",
"integrity": "sha1-rAr3FoBFjYpjeNDQ0FCrFAfTVZY="
},
"vuex-class": {
"version": "0.3.2",
"resolved": "http://npm.job.qinqinxiaobao.com/vuex-class/-/vuex-class-0.3.2.tgz",
"integrity": "sha1-x+lqB2wWghN9TSOo3P3GPyIOF6g="
},
"wamp.js": {
"version": "0.2.6",
"resolved": "http://npm.job.qinqinxiaobao.com/wamp.js/-/wamp.js-0.2.6.tgz",
"integrity": "sha512-ZQz4t94m2faHJnyFilRCmd3RaXXLOb8Z17LqgQeWoMJiyzrVkOwbl8n1CP5L3U7+M+8P83jjlPKwzKKQzN7DEA==",
"requires": {
"tweetnacl": "^1.0.1",
"ws": "^7.1.0"
}
},
"ws": {
"version": "7.5.3",
"resolved": "http://npm.job.qinqinxiaobao.com/ws/-/ws-7.5.3.tgz",
"integrity": "sha1-Fgg1tjx9l7+rQY/BuKn87SrAGnQ="
},
"xchat-client": {
"version": "2.0.20",
"resolved": "http://npm.job.qinqinxiaobao.com/xchat-client/-/xchat-client-2.0.20.tgz",
"integrity": "sha512-Fn9vay5sL2H55Vr4zndhvo7O1Ieskx9/ES9g/yWLfr8kiYMa0Q4uKh5ZJltTxk8xzAqXFHPbZFYJPJFZoyJ69g==",
"requires": {
"wamp.js": "^0.2.0"
}
}
}
}
{
"dependencies": {
"@types/sha1": "^1.1.2",
"axios": "^0.19.2",
"qs": "^6.9.3",
"sha1": "^1.1.1",
"vuex-class": "^0.3.2",
"xchat-client": "^2.0.19"
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment