123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- <script>
- import { useScopedSlot, messageToHtml, clearHtmlExcludeImg } from "utils";
- import ChatArea from "chatarea";
- // 引入css样式
- import "chatarea/lib/ChatArea.css";
- const command = (command, val) => {
- document.execCommand(command, false, val);
- };
- const selection = window.getSelection();
- let range;
- let emojiData = [];
- let isInitTool = false;
- export default {
- name: "LemonEditor",
- inject: {
- IMUI: {
- from: "IMUI",
- default() {
- return this;
- },
- },
- },
- components: {},
- props: {
- tools: {
- type: Array,
- default: () => [],
- },
- sendText: {
- type: String,
- default: "发 送",
- },
- wrapKey: {
- type: Function,
- default: function(e) {
- return e.keyCode == 13 && e.ctrlKey === true;
- },
- },
- sendKey: {
- type: Function,
- default(e) {
- return e.keyCode == 13 && e.ctrlKey == false && e.shiftKey == false;
- },
- },
- },
- data() {
- this.clipboardBlob = null;
- return {
- //剪切板图片URL
- clipboardUrl: "",
- submitDisabled: true,
- //proxyTools: [],
- accept: "",
- chatArea: null,
- };
- },
- created() {
- this.IMUI.$on("change-contact", () => {
- this.closeClipboardImage();
- });
- this.$nextTick(() => {
- this.chatArea = new ChatArea({
- elm: this.$refs.textarea, // 从dom上抓取一个元素将其改造
- userList: [
- // 当输入@键时弹出人员选择的列表
- ],
- userProps: {
- // 人员列表数据结构 转接差异key值
- id: "id",
- name: "displayName",
- avatar: "avatar",
- pinyin: "name_py",
- },
- placeholder: "",
- needCallSpace: true,
- wrapKeyFun: event => this.wrapKey(event),
- sendKeyFun: event => this.sendKey(event),
- });
- this.chatArea.richText.addEventListener("blur", e => {
- setTimeout(this.chatArea.winClick, 200);
- });
- // 监听发送键
- this.chatArea.enterSend = () => {
- // 在此处执行你对发送消息的处理
- if (this.submitDisabled == false) {
- this._handleSend();
- }
- };
- });
- },
- render() {
- 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"
- multiple="multiple"
- ref="fileInput"
- accept={this.accept}
- onChange={this._handleChangeFile}
- />
- <div class="lemon-editor__tool">
- <div class="lemon-editor__tool-left">{toolLeft}</div>
- <div class="lemon-editor__tool-right">{toolRight}</div>
- </div>
- <div class="lemon-editor__inner">
- <div
- class="lemon-editor__input"
- ref="textarea"
- on-keyup={this._handleKeyup}
- on-keydown={this._handleKeydown}
- on-paste={this._handlePaste}
- on-click={this._handleClick}
- on-input={this._handleInput}
- on-drop={this._handleDrop}
- spellcheck="false"
- />
- </div>
- <div class="lemon-editor__footer">
- <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>
- </div>
- );
- },
- computed: {
- proxyTools() {
- if (!this.tools) 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(this.tools)) {
- const indexMap = {
- emoji: 0,
- uploadFile: 1,
- uploadImage: 2,
- };
- const indexKeys = Object.keys(indexMap);
- tools = this.tools.map(item => {
- if (indexKeys.includes(item.name)) {
- return {
- ...defaultTools[indexMap[item.name]],
- ...item,
- };
- }
- return item;
- });
- } else {
- tools = defaultTools;
- }
- return tools;
- },
- },
- methods: {
- closeClipboardImage() {
- this.clipboardUrl = "";
- this.clipboardBlob = null;
- },
- sendClipboardImage() {
- if (!this.clipboardBlob) return;
- this.$emit("upload", this.clipboardBlob);
- this.closeClipboardImage();
- },
- saveRangeToLast() {
- if (!range) {
- range = document.createRange();
- }
- range.selectNodeContents(textarea.value);
- range.collapse(false);
- selection.removeAllRanges();
- selection.addRange(range);
- },
- inertContent(val, toLast = false) {
- if (toLast) saveRangeToLast();
- this.focusRange();
- command("insertHTML", val);
- this.saveRange();
- },
- saveRange() {
- range = selection.getRangeAt(0);
- },
- focusRange() {
- this.$refs.textarea.focus();
- if (range) {
- selection.removeAllRanges();
- selection.addRange(range);
- }
- },
- _handleClick() {
- this.saveRange();
- },
- _handleInput() {
- this._checkSubmitDisabled();
- },
- _handleDrop(e) {
- e.preventDefault();
- const { dataTransfer } = e;
- const { files } = dataTransfer;
- Array.from(files).forEach(file => {
- this.$emit("upload", file);
- });
- },
- _renderEmojiTabs() {
- const renderImageGrid = items => {
- return items.map(item => (
- <img
- src={item.src}
- title={item.title}
- class="lemon-editor__emoji-item"
- on-click={() => this._handleSelectEmoji(item)}
- />
- ));
- };
- if (emojiData[0].label) {
- const nodes = emojiData.map((item, index) => {
- return (
- <div slot="tab-pane" index={index} tab={item.label}>
- {renderImageGrid(item.children)}
- </div>
- );
- });
- return <lemon-tabs style="width: 412px">{nodes}</lemon-tabs>;
- } else {
- return (
- <div class="lemon-tabs-content" style="width:406px">
- {renderImageGrid(emojiData)}
- </div>
- );
- }
- },
- _handleSelectEmoji(item) {
- this.chatArea.insertHtml(
- `<img emoji-name="${item.name}" src="${item.src}"></img>`,
- );
- this._checkSubmitDisabled();
- },
- async selectFile(accept) {
- this.accept = accept;
- await this.$nextTick();
- this.$refs.fileInput.click();
- },
- _handlePaste(e) {
- e.preventDefault();
- const clipboardData = e.clipboardData || window.clipboardData;
- const text = clipboardData.getData("Text");
- if (text) {
- this.submitDisabled = false;
- } 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.saveRange();
- this._checkSubmitDisabled();
- },
- _handleKeydown(e) {
- if (e.keyCode == 13 || (e.keyCode == 13 && e.shiftKey)) {
- e.preventDefault();
- }
- if (this.wrapKey(e)) {
- e.preventDefault();
- command("insertLineBreak");
- }
- },
- getFormatValue() {
- // return toEmojiName(
- // this.$refs.textarea.innerHTML
- // .replace(/<br>|<\/br>/, "")
- // .replace(/<div>|<p>/g, "\r\n")
- // .replace(/<\/div>|<\/p>/g, "")
- // );
- return this.IMUI.emojiImageToName(this.chatArea.getHtml());
- },
- _checkSubmitDisabled() {
- this.submitDisabled = !clearHtmlExcludeImg(
- this.chatArea.getHtml().trim(),
- );
- },
- _handleSend(e) {
- const text = this.getFormatValue();
- this.$emit("send", text);
- this.clear();
- this._checkSubmitDisabled();
- },
- _handleChangeFile(e) {
- const { fileInput } = this.$refs;
- Array.from(fileInput.files).forEach(file => {
- this.$emit("upload", file);
- });
- fileInput.value = "";
- },
- clear() {
- this.chatArea.clear();
- },
- initEmoji(data) {
- emojiData = data;
- this.$forceUpdate();
- },
- setValue(val) {
- this.chatArea.clear(this.IMUI.emojiNameToImage(val));
- this._checkSubmitDisabled();
- },
- },
- };
- </script>
- <style lang="stylus">
- @import '~styles/utils/index'
- gap = 10px;
- +b(lemon-editor)
- height 200px
- position relative
- flex-column()
- +e(tool)
- display flex
- height 40px
- align-items center
- 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
- border none
- outline none
- padding 0 gap
- scrollbar-light()
- .chat-rich-text
- border-radius 0
- min-height 100px
- padding 0
- .chat-rich-text *
- .chat-area-pc *
- font-size 14px
- p,div
- margin 0
- img
- height 20px
- padding 0 2px
- pointer-events none
- position relative
- top -1px
- vertical-align middle
- +e(footer)
- display flex
- height 52px
- justify-content flex-end
- padding 0 gap
- align-items center
- +e(tip)
- margin-right 10px
- font-size 12px
- color #999
- user-select none
- +e(emoji)
- user-select none
- .lemon-popover
- background #f6f6f6
- .lemon-popover__content
- padding 0
- .lemon-popover__arrow
- background #f6f6f6
- .lemon-tabs-content
- box-sizing border-box
- padding 8px
- height 200px
- overflow-x hidden
- overflow-y auto
- scrollbar-light()
- margin-bottom 8px
- +e(emoji-item)
- cursor pointer
- width 22px
- padding 4px
- border-radius 4px
- &:hover
- background #e9e9e9
- </style>
|