141 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { get, set, del, update, createStore, values } from 'idb-keyval'
 | |
| import { Span, Button, List, ListItem, Input, createElement } from './weigets.js'
 | |
| 
 | |
| // 先不划分频道, 只有一个公共聊天室
 | |
| export default class Chat {
 | |
|     constructor({ name, EventListeners = {}, onsend, onexit }) {
 | |
|         this.event = { onsend, onexit }
 | |
|         this.store = createStore(`db-chat-${name}`, `store-chat-${name}`)
 | |
|         this.ul = List({ classList: ['chat-list'] })
 | |
|         this.EventListeners = EventListeners
 | |
|         document.body.appendChild(createElement({
 | |
|             children: [
 | |
|                 this.ul,
 | |
|                 createElement({
 | |
|                     type: 'text',
 | |
|                     placeholder: '输入聊天内容',
 | |
|                     style: {
 | |
|                         height: '5rem',
 | |
|                         margin: '1rem 2rem',
 | |
|                         padding: '1rem',
 | |
|                         boxSizing: 'border-box',
 | |
|                         boxShadow: '0 0 1rem #eee',
 | |
|                     },
 | |
|                     onkeydown: event => {
 | |
|                         const text = event.target.value.trim()
 | |
|                         if (text && event.ctrlKey && event.key === 'Enter') {
 | |
|                             this.发送消息(text)
 | |
|                             event.target.value = ''
 | |
|                         }
 | |
|                     }
 | |
|                 }, 'textarea'),
 | |
|                 Button({
 | |
|                     textContent: '发送(ctrl+Enter)',
 | |
|                     onclick: event => {
 | |
|                         const text = event.target.previousSibling.value.trim()
 | |
|                         if (text) {
 | |
|                             this.发送消息(text)
 | |
|                             event.target.previousSibling.value = ''
 | |
|                         }
 | |
|                     },
 | |
|                     style: {
 | |
|                         margin: '1rem 2rem',
 | |
|                         padding: '.5rem 1rem',
 | |
|                         boxSizing: 'border-box',
 | |
|                         boxShadow: '0 0 1rem #eee',
 | |
|                         borderRadius: '1rem',
 | |
|                     }
 | |
|                 }),
 | |
|             ]
 | |
|         }))
 | |
|         // 写入 css 样式到 head
 | |
|         const style = document.createElement('style')
 | |
|         style.innerText = `
 | |
|             ul.chat-list {
 | |
|                 max-height: 70vh;
 | |
|                 overflow-y: auto;
 | |
|             }
 | |
|             ul.chat-list > li > span {
 | |
|                 cursor: pointer;
 | |
|             }
 | |
|             ul.chat-list > li.play > span {
 | |
|                 color: #02be08;
 | |
|             }
 | |
|             ul.chat-list > li.cache::marker {
 | |
|                 color: #02be08;
 | |
|                 font-size: 1em;
 | |
|                 contentx: '⚡';
 | |
|             }
 | |
|             ul.chat-list > li.disable {
 | |
|                 color: #888;
 | |
|             }
 | |
|         `
 | |
|         document.head.appendChild(style)
 | |
|         this.载入消息()
 | |
|     }
 | |
|     // 收到应答(对方确认消息已被接收)
 | |
|     answer(data) {
 | |
|         const { name, text, time, type } = data
 | |
|         const item = this.add({ name, text, time, type })
 | |
|         item.classList.add('disable')
 | |
|     }
 | |
|     // 添加一条消息
 | |
|     add({ name, text, time, type }) {
 | |
|         const item = ListItem({
 | |
|             classList: [type],
 | |
|             children: [
 | |
|                 Span({ innerText: `${name} ${time} ${text}` })
 | |
|             ]
 | |
|         })
 | |
|         this.ul.appendChild(item)
 | |
|         this.ul.scrollTop = this.ul.scrollHeight
 | |
|         return item
 | |
|     }
 | |
|     send(text) {
 | |
|         if (this.event.onsend) {
 | |
|             this.event.onsend(text)
 | |
|         }
 | |
|     }
 | |
|     添加元素(data) {
 | |
|         this.ul.appendChild(ListItem({
 | |
|             children: [
 | |
|                 Span({ innerText: `${data.name} ${data.time} ${data.text}` })
 | |
|             ]
 | |
|         }))
 | |
|     }
 | |
|     存储消息(data) {
 | |
|         const { name, text, time, type } = data
 | |
|         const id = window.crypto.randomUUID()
 | |
|         const item = { id, name, text, time, type }
 | |
|         set(id, item, this.store)
 | |
|     }
 | |
|     载入消息() {
 | |
|         values(this.store).then(items => {
 | |
|             items.map(item => {
 | |
|                 item.timestamp = new Date(item.time).getTime()
 | |
|                 return item
 | |
|             }).sort((a, b) => a.timestamp - b.timestamp).forEach(item => {
 | |
|                 this.添加元素(item)
 | |
|             })
 | |
|         })
 | |
|     }
 | |
|     发送消息(text) {
 | |
|         const name = '我'
 | |
|         const time = new Date().toLocaleString()
 | |
|         console.log('发送消息', { name, text, time })
 | |
|         const type = 'text'
 | |
|         this.添加元素({ name, text, time, type })
 | |
|         this.存储消息({ name, text, time, type })
 | |
|         this.send({ name, text, time, type })
 | |
|     }
 | |
|     收到消息(data) {
 | |
|         const { name, text, time, type } = data
 | |
|         this.add({ name, text, time, type })
 | |
|     }
 | |
|     // 退出
 | |
|     exit() {
 | |
|         if (this.event.onexit) {
 | |
|             this.event.onexit()
 | |
|         }
 | |
|     }
 | |
| } |