chat-window.js 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. /* ============================================================
  2. * lib/components/chat-window.js · 仿微信一对一聊天窗
  3. * 用法:
  4. * <link rel="stylesheet" href=".../lib/components/chat-window.css">
  5. * <chat-window data-chat='{...}'></chat-window> // 短数据
  6. * <script type="application/json" id="chat-x"> {...} </script>
  7. * <chat-window data-ref="#chat-x"></chat-window> // 长数据
  8. * 消息类型:text / transfer(转账卡) / time(时间) / divider(分隔) / image / quote
  9. * 全部颜色来自 tokens.css 的 --chat-*(双主题自适应)。
  10. * ============================================================ */
  11. (function () {
  12. function esc(s) {
  13. return String(s == null ? '' : s)
  14. .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  15. }
  16. function nl(s) {
  17. return esc(s).replace(/\n/g, '<br>');
  18. }
  19. function parse(el) {
  20. if (el.dataset.chat) { try { return JSON.parse(el.dataset.chat); } catch (e) { return null; } }
  21. if (el.dataset.ref) {
  22. var src = document.querySelector(el.dataset.ref);
  23. if (src) { try { return JSON.parse(src.textContent); } catch (e) { return null; } }
  24. }
  25. return null;
  26. }
  27. function renderMsg(m) {
  28. var side = m.side === 'out' ? 'out' : 'in';
  29. if (m.type === 'time') {
  30. return '<div class="cw-time">' + esc(m.text) + '</div>';
  31. }
  32. if (m.type === 'divider') {
  33. return '<div class="cw-divider">' + esc(m.text) + '</div>';
  34. }
  35. // 带气泡的消息
  36. var ava = '<div class="cw-ava">' + esc(m.avatar || (side === 'out' ? '我' : '')) + '</div>';
  37. var inner = '';
  38. if (m.type === 'transfer') {
  39. inner = '<div class="cw-bub cw-bub-transfer">'
  40. + '<span class="cw-tf-label">' + esc(m.text || '微信转账') + '</span>'
  41. + '<span class="cw-tf-amt">' + esc(m.amount || '') + '</span>'
  42. + '</div>';
  43. } else if (m.type === 'image') {
  44. inner = '<div class="cw-bub"><img class="cw-img" src="' + esc(m.src) + '" alt="">'
  45. + (m.caption ? '<div class="cw-cap">' + esc(m.caption) + '</div>' : '')
  46. + '</div>';
  47. } else if (m.type === 'quote') {
  48. inner = '<div class="cw-bub cw-bub-quote">' + nl(m.text)
  49. + (m.source ? '<div class="cw-q-src">' + esc(m.source) + '</div>' : '')
  50. + '</div>';
  51. } else {
  52. var body = nl(m.text);
  53. if (m.bold) body = '<b>' + body + '</b>';
  54. inner = '<div class="cw-bub">' + body + '</div>';
  55. }
  56. return '<div class="cw-msg ' + side + '">' + (side === 'in' ? ava + inner : inner + ava) + '</div>';
  57. }
  58. function render(el, d) {
  59. if (!d) { el.innerHTML = '<div class="cw cw-empty">数据缺失</div>'; return; }
  60. var msgs = (d.msgs || []).map(renderMsg).join('');
  61. el.innerHTML =
  62. '<div class="cw">' +
  63. (d.title || d.subtitle ? (
  64. '<div class="cw-bar">' + esc(d.title || '') +
  65. (d.subtitle ? '<span class="cw-sub">' + esc(d.subtitle) + '</span>' : '') +
  66. '</div>'
  67. ) : '') +
  68. '<div class="cw-body">' + msgs + '</div>' +
  69. (d.footer ? '<div class="cw-foot">' + esc(d.footer) + '</div>' : '') +
  70. '</div>';
  71. }
  72. var ChatWindow = function () {
  73. return Reflect.construct(HTMLElement, [], ChatWindow);
  74. };
  75. ChatWindow.prototype = Object.create(HTMLElement.prototype);
  76. ChatWindow.prototype.constructor = ChatWindow;
  77. ChatWindow.prototype.connectedCallback = function () {
  78. var self = this;
  79. var d = parse(this);
  80. if (!d && this.dataset.ref) {
  81. // ref 指向的 JSON 可能晚于组件加载,延时再读一次
  82. setTimeout(function () { render(self, parse(self)); }, 0);
  83. } else {
  84. render(this, d);
  85. }
  86. };
  87. if (!customElements.get('chat-window')) {
  88. customElements.define('chat-window', ChatWindow);
  89. }
  90. })();