@bbki.ng/bb-msg-history 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/index.ts +13 -8
package/README.md
CHANGED
|
@@ -1,2 +1,119 @@
|
|
|
1
1
|
# bb-msg-history
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
Chat-style message history web component. Render conversations from plain text, with avatars, bubbles, and smooth animations.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @bbki.ng/bb-msg-history
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
CDN:
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@bbki.ng/bb-msg-history@latest/dist/index.js"></script>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Place messages inside the element using the `author: text` format, one per line:
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<bb-msg-history>
|
|
23
|
+
alice: Hey, are you free this weekend?
|
|
24
|
+
bob: Sounds good! When?
|
|
25
|
+
alice: Saturday morning, around 10?
|
|
26
|
+
bob: Perfect. See you then!
|
|
27
|
+
</bb-msg-history>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Message Format
|
|
31
|
+
|
|
32
|
+
Each message is a line with the author name, a colon, and the message text:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
<author>: <message text>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Blank lines and lines without a colon are ignored.
|
|
39
|
+
|
|
40
|
+
## Built-in Authors
|
|
41
|
+
|
|
42
|
+
Two authors have built-in SVG avatars and are pre-configured with a specific side:
|
|
43
|
+
|
|
44
|
+
| Author | Side | Bubble Color |
|
|
45
|
+
|--------|------|--------------|
|
|
46
|
+
| `bbki.ng` | right | light gray |
|
|
47
|
+
| `xwy` | left | light pink |
|
|
48
|
+
|
|
49
|
+
Any other author name is placed on the **left** side and receives a letter avatar (first character of the name).
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
- Plain-text message format — no JSON or attributes needed
|
|
54
|
+
- Left/right bubble layout based on author
|
|
55
|
+
- SVG avatars with hover tooltip showing the author name
|
|
56
|
+
- Auto-scroll to the latest message on render
|
|
57
|
+
- Fade-in animation per message row
|
|
58
|
+
- Dark mode support via `prefers-color-scheme`
|
|
59
|
+
- Mobile responsive layout
|
|
60
|
+
- `prefers-reduced-motion` support
|
|
61
|
+
- Graceful degradation to `<pre>` when Custom Elements are unsupported
|
|
62
|
+
|
|
63
|
+
## Examples
|
|
64
|
+
|
|
65
|
+
### Basic conversation
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<bb-msg-history>
|
|
69
|
+
bbki.ng: 谁呀?
|
|
70
|
+
xwy: 谁谁谁,你猴爷爷!
|
|
71
|
+
bbki.ng: foo
|
|
72
|
+
xwy: bar
|
|
73
|
+
</bb-msg-history>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Unknown / custom authors
|
|
77
|
+
|
|
78
|
+
Authors not listed in the built-in config receive a letter avatar and appear on the left:
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<bb-msg-history>
|
|
82
|
+
alice: Hey!
|
|
83
|
+
bob: Hi there!
|
|
84
|
+
alice: How are you?
|
|
85
|
+
</bb-msg-history>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Full page example
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<!DOCTYPE html>
|
|
92
|
+
<html>
|
|
93
|
+
<head>
|
|
94
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@bbki.ng/bb-msg-history@latest/dist/index.js"></script>
|
|
95
|
+
</head>
|
|
96
|
+
<body>
|
|
97
|
+
<bb-msg-history>
|
|
98
|
+
alice: Hey, are you free this weekend?
|
|
99
|
+
bob: Yeah, what's up?
|
|
100
|
+
alice: Want to grab coffee?
|
|
101
|
+
bob: Sounds good! Saturday morning?
|
|
102
|
+
alice: Perfect, see you then!
|
|
103
|
+
</bb-msg-history>
|
|
104
|
+
</body>
|
|
105
|
+
</html>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
See `example/` directory for a full demo.
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm install
|
|
114
|
+
npm run prepare
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const THEME={gray:{50:"#f9fafb",100:"#f3f4f6",200:"#e5e7eb",300:"#d1d5db",400:"#9ca3af",500:"#6b7280",600:"#4b5563",700:"#374151",800:"#1f2937",900:"#111827"},blue:{600:"#000000"},red:{50:"#fef2f2",100:"#fee2e2",200:"#fecaca",300:"#fca5a5",400:"#f87171",500:"#ef4444",600:"#dc2626"},yyPink:{50:"#fdf4f4",100:"#fbd1d2",150:"#f8babc"}},AUTHOR_CONFIG={"bbki.ng":{avatar:`<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 48 48" fill="none"><path d="M29.1152 21.3106C32.0605 21.3106 34.4481 18.9101 34.4481 15.9489V24.6457C34.4481 25.7585 33.5508 26.6607 32.444 26.6607H15.1207C14.0138 26.6607 13.1166 25.7585 13.1166 24.6457V15.9489C13.1166 18.9101 15.5042 21.3106 18.4494 21.3106C21.3947 21.3106 23.7823 18.9101 23.7823 15.9489C23.7823 18.9101 26.17 21.3106 29.1152 21.3106Z" fill="${THEME.gray[400]}"/><path d="M23.7823 15.9373L23.7823 15.9489C23.7823 15.9451 23.7823 15.9412 23.7823 15.9373Z" fill="${THEME.gray[400]}"/><path d="M23.1143 28.004C23.1205 30.9598 25.5057 33.3541 28.4472 33.3541C31.3886 33.3541 33.7738 30.9598 33.7801 28.004H23.1143Z" fill="${THEME.gray[400]}"/><path d="M13.7846 28.004C13.7846 28.0079 13.7846 28.0117 13.7846 28.0156C13.7908 30.9714 16.1761 33.3657 19.1175 33.3657C22.0589 33.3657 24.4442 30.9714 24.4504 28.0156H13.7846V28.004Z" fill="${THEME.gray[400]}"/><path d="M14.4527 15.9373C14.4527 16.6792 13.8545 17.2806 13.1166 17.2806C12.3786 17.2806 11.7805 16.6792 11.7805 15.9373C11.7805 15.1954 12.3786 14.594 13.1166 14.594C13.8545 14.594 14.4527 15.1954 14.4527 15.9373Z" fill="${THEME.gray[400]}"/><path d="M25.1184 15.2657C25.1184 16.0076 24.5202 16.609 23.7823 16.609C23.0444 16.609 22.4462 16.0076 22.4462 15.2657C22.4462 14.5238 23.0444 13.9224 23.7823 13.9224C24.5202 13.9224 25.1184 14.5238 25.1184 15.2657Z" fill="${THEME.gray[400]}"/><path d="M35.7842 15.9373C35.7842 16.6792 35.186 17.2806 34.4481 17.2806C33.7102 17.2806 33.112 16.6792 33.112 15.9373C33.112 15.1954 33.7102 14.594 34.4481 14.594C35.186 14.594 35.7842 15.1954 35.7842 15.9373Z" fill="${THEME.gray[400]}"/></svg>`,bubbleColor:THEME.gray[100],textColor:THEME.gray[900],side:"right"},xwy:{avatar:`<svg width="28" height="28" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.821 17.5305C10.709 18.17 9.68345 19.4423 9.22624 20.1359C9.11159 20.3099 9.21615 20.5428 9.42038 20.5839L12.67 21.2381C12.8291 21.2702 12.9328 21.4275 12.9084 21.5879C11.3004 32.1653 21.5275 36.7547 28.6638 33.0597C28.7443 33.018 28.8408 33.0139 28.9245 33.0487C32.8032 34.6598 35.967 34.5662 37.8217 34.3099C38.131 34.2671 38.1505 33.841 37.855 33.7401C29.1343 30.7633 26.0152 24.5245 25.5144 18.8022C25.3835 17.3066 23.8172 13.2016 19.2675 13.0058C15.7934 12.8563 13.6137 15.6103 13.0319 17.325C12.9986 17.4231 12.9201 17.5004 12.821 17.5305Z" fill="${THEME.yyPink[100]}"/><circle cx="17.6178" cy="18.2688" r="0.995689" fill="white"/></svg>`,bubbleColor:THEME.yyPink[50],textColor:THEME.gray[900],side:"left"},"小乌鸦":{avatar:`<svg width="28" height="28" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.821 17.5305C10.709 18.17 9.68345 19.4423 9.22624 20.1359C9.11159 20.3099 9.21615 20.5428 9.42038 20.5839L12.67 21.2381C12.8291 21.2702 12.9328 21.4275 12.9084 21.5879C11.3004 32.1653 21.5275 36.7547 28.6638 33.0597C28.7443 33.018 28.8408 33.0139 28.9245 33.0487C32.8032 34.6598 35.967 34.5662 37.8217 34.3099C38.131 34.2671 38.1505 33.841 37.855 33.7401C29.1343 30.7633 26.0152 24.5245 25.5144 18.8022C25.3835 17.3066 23.8172 13.2016 19.2675 13.0058C15.7934 12.8563 13.6137 15.6103 13.0319 17.325C12.9986 17.4231 12.9201 17.5004 12.821 17.5305Z" fill="${THEME.yyPink[100]}"/><circle cx="17.6178" cy="18.2688" r="0.995689" fill="white"/></svg>`,bubbleColor:THEME.yyPink[50],textColor:THEME.gray[900],side:"left"}},FIRST_CHAR_AVATAR_AUTHORS=new Set(["小乌鸦"]);class BBMsgHistory extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render(),this._setupResizeObserver()}disconnectedCallback(){this._resizeObserver?.disconnect()}_setupResizeObserver(){"ResizeObserver"in window&&(this._resizeObserver=new ResizeObserver(()=>{this._adjustLayout()}),this._resizeObserver.observe(this))}_adjustLayout(){}parseMessages(){const n=this.textContent||"",e=[];for(const t of n.split("\n")){const n=t.trim();if(!n.startsWith("- "))continue;const r=n.slice(2).trim(),a=r.indexOf(":");if(a<=0)continue;const o=r.slice(0,a).trim(),i=r.slice(a+1).trim();o&&i&&e.push({author:o,text:i})}return e}getAuthorConfig(n){if(FIRST_CHAR_AVATAR_AUTHORS.has(n)){const e=AUTHOR_CONFIG[n],t=n.charAt(0);return{...e||{bubbleColor:THEME.gray[50],textColor:THEME.gray[900],side:"left"},avatar:this._generateLetterAvatar(t),isCustomAvatar:!1}}if(AUTHOR_CONFIG[n])return{...AUTHOR_CONFIG[n],isCustomAvatar:!0};for(const[e,t]of Object.entries(AUTHOR_CONFIG))if(n.includes(e)||e.includes(n))return{...t,isCustomAvatar:!0};const e=n.charAt(0).toUpperCase();return{avatar:this._generateLetterAvatar(e),bubbleColor:THEME.gray[50],textColor:THEME.gray[900],side:"left",isCustomAvatar:!1}}_generateLetterAvatar(n){return`<div style="\n width: 100%; \n height: 100%; \n display: flex; \n align-items: center; \n justify-content: center; \n background: #ffffff; \n color: ${THEME.gray[600]}; \n font-size: 14px; \n font-weight: 600;\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;\n ">${n}</div>`}escapeHtml(n){const e=document.createElement("div");return e.textContent=n,e.innerHTML}render(){const n=this.parseMessages();if(0===n.length)return void this._renderEmpty();let e="";const t=n.map(({author:n,text:t})=>{const r=this.getAuthorConfig(n),a=n!==e;e=n;const o=a,i=r.side,s=!a,l=`\n <div class="avatar-wrapper ${o?"":"avatar-wrapper--hidden"}" \n data-author="${this.escapeHtml(n)}">\n <div class="avatar">${r.avatar}</div>\n <div class="avatar-tooltip">${this.escapeHtml(n)}</div>\n </div>\n `;return`\n <div class="msg-row msg-row--${i} ${s?"msg-row--subsequent":""}">\n ${"left"===i?l:""}\n \n <div class="msg-content">\n <div class="msg-bubble msg-bubble--${i}" \n style="background-color: ${r.bubbleColor}; color: ${r.textColor};">\n ${this.escapeHtml(t)}\n </div>\n </div>\n \n ${"right"===i?l:""}\n </div>\n `}).join("");this.shadowRoot.innerHTML=`\n <style>\n :host {\n display: block;\n font-family: "PT Sans", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,\n "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,\n "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",\n "Noto Color Emoji";\n --bb-bg-color: ${THEME.gray[50]};\n }\n\n .history {\n max-width: 640px;\n margin: 0 auto;\n padding: 1rem;\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n max-height: 600px;\n overflow-y: auto;\n scroll-behavior: smooth;\n background-color: transparent;\n border-radius: 0.5rem;\n }\n\n .history::-webkit-scrollbar {\n width: 0.5rem;\n }\n .history::-webkit-scrollbar-track {\n background: transparent;\n }\n .history::-webkit-scrollbar-thumb {\n background-color: ${THEME.gray[300]};\n border-radius: 0.25rem;\n }\n\n /* 消息行布局 */\n .msg-row {\n display: flex;\n align-items: flex-end;\n gap: 0.5rem;\n max-width: 80%;\n animation: fadeIn 0.2s ease-out;\n }\n\n @keyframes fadeIn {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n .msg-row--left {\n align-self: flex-start;\n margin-right: auto;\n }\n\n .msg-row--right {\n align-self: flex-end;\n margin-left: auto;\n }\n\n .msg-row--subsequent {\n margin-top: 0.125rem;\n }\n\n /* 头像容器 */\n .avatar-wrapper {\n position: relative;\n flex-shrink: 0;\n width: 1.75rem;\n height: 1.75rem;\n border-radius: 50%;\n overflow: hidden;\n background: #ffffff;\n cursor: help;\n transition: transform 0.15s ease;\n }\n\n .avatar-wrapper:hover {\n transform: scale(1.1);\n }\n\n .avatar-wrapper--hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .avatar {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .avatar svg {\n width: 100%;\n height: 100%;\n }\n\n /* 悬浮显示名字 */\n .avatar-tooltip {\n position: absolute;\n bottom: calc(100% + 0.5rem);\n left: 50%;\n transform: translateX(-50%) scale(0.9);\n padding: 0.25rem 0.5rem;\n background: ${THEME.gray[800]};\n color: ${THEME.gray[50]};\n font-size: 0.75rem;\n border-radius: 0.25rem;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition: all 0.15s ease;\n pointer-events: none;\n z-index: 10;\n font-weight: 500;\n letter-spacing: 0.02em;\n }\n\n .avatar-tooltip::after {\n content: '';\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 4px solid transparent;\n border-top-color: ${THEME.gray[800]};\n }\n\n .avatar-wrapper:hover .avatar-tooltip {\n opacity: 1;\n visibility: visible;\n transform: translateX(-50%) scale(1);\n }\n\n /* 消息内容区 */\n .msg-content {\n display: flex;\n flex-direction: column;\n }\n\n .msg-bubble {\n padding: 0.625rem 0.875rem;\n font-size: 0.9375rem;\n line-height: 1.5;\n word-wrap: break-word;\n overflow-wrap: anywhere;\n word-break: break-word;\n border-radius: 1rem;\n }\n\n /* 左侧气泡 */\n .msg-bubble--left {\n border-bottom-left-radius: 0.25rem;\n background-color: ${THEME.gray[200]};\n color: ${THEME.gray[900]};\n }\n\n /* 右侧气泡 */\n .msg-bubble--right {\n border-bottom-right-radius: 0.25rem;\n /* 移除边框 */\n }\n\n /* 空状态 */\n .empty-state {\n text-align: center;\n padding: 2rem;\n color: ${THEME.gray[400]};\n font-size: 0.875rem;\n }\n\n /* 移动端 */\n @media (max-width: 480px) {\n .history {\n padding: 0.75rem;\n max-height: 70vh;\n }\n \n .msg-row {\n max-width: 85%;\n }\n \n .msg-bubble {\n font-size: 0.9375rem;\n padding: 0.5rem 0.75rem;\n }\n \n .avatar-wrapper {\n width: 1.5rem;\n height: 1.5rem;\n }\n }\n\n /* 暗色模式 */\n @media (prefers-color-scheme: dark) {\n :host {\n --bb-bg-color: ${THEME.gray[900]};\n }\n \n .msg-bubble--left {\n background-color: ${THEME.gray[700]} !important;\n color: ${THEME.gray[100]} !important;\n }\n \n .msg-bubble--right {\n background-color: ${THEME.gray[800]} !important;\n color: ${THEME.gray[100]} !important;\n border-color: ${THEME.gray[700]};\n }\n \n .avatar-wrapper {\n background: #ffffff;\n }\n \n .avatar-tooltip {\n background: ${THEME.gray[200]};\n color: ${THEME.gray[900]};\n }\n \n .avatar-tooltip::after {\n border-top-color: ${THEME.gray[200]};\n }\n }\n\n /* 减少动画 */\n @media (prefers-reduced-motion: reduce) {\n .msg-row {\n animation: none;\n }\n }\n </style>\n <div class="history" role="log" aria-live="polite" aria-label="Message history">\n ${t}\n </div>\n `,requestAnimationFrame(()=>{const n=this.shadowRoot.querySelector(".history");n&&(n.scrollTop=n.scrollHeight)})}_renderEmpty(){this.shadowRoot.innerHTML=`\n <style>\n :host { display: block; }\n .empty-state {\n text-align: center;\n padding: 2rem;\n color: ${THEME.gray[400]};\n font-size: 0.875rem;\n font-family: inherit;\n }\n </style>\n <div class="empty-state">No messages</div>\n `}}function initBBMsgHistory(){try{customElements.get("bb-msg-history")||customElements.define("bb-msg-history",BBMsgHistory)}catch(n){console.warn("BBMsgHistory registration failed, falling back to plain text:",n),document.querySelectorAll("bb-msg-history").forEach(n=>{const e=document.createElement("pre");e.style.cssText=`\n background: ${THEME.gray[100]};\n padding: 1rem;\n border-radius: 0.5rem;\n overflow-x: auto;\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;\n font-size: 0.875rem;\n line-height: 1.5;\n color: ${THEME.gray[900]};\n margin: 0;\n white-space: pre-wrap;\n word-wrap: break-word;\n border: 1px solid ${THEME.gray[200]};\n `,e.textContent=n.textContent||"",n.replaceWith(e)})}}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",initBBMsgHistory):initBBMsgHistory();export{BBMsgHistory};
|
|
1
|
+
const THEME={gray:{50:"#f9fafb",100:"#f3f4f6",200:"#e5e7eb",300:"#d1d5db",400:"#9ca3af",500:"#6b7280",600:"#4b5563",700:"#374151",800:"#1f2937",900:"#111827"},blue:{600:"#000000"},red:{50:"#fef2f2",100:"#fee2e2",200:"#fecaca",300:"#fca5a5",400:"#f87171",500:"#ef4444",600:"#dc2626"},yyPink:{50:"#fdf4f4",100:"#fbd1d2",150:"#f8babc"}},AUTHOR_CONFIG={"bbki.ng":{avatar:`<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 48 48" fill="none"><path d="M29.1152 21.3106C32.0605 21.3106 34.4481 18.9101 34.4481 15.9489V24.6457C34.4481 25.7585 33.5508 26.6607 32.444 26.6607H15.1207C14.0138 26.6607 13.1166 25.7585 13.1166 24.6457V15.9489C13.1166 18.9101 15.5042 21.3106 18.4494 21.3106C21.3947 21.3106 23.7823 18.9101 23.7823 15.9489C23.7823 18.9101 26.17 21.3106 29.1152 21.3106Z" fill="${THEME.gray[400]}"/><path d="M23.7823 15.9373L23.7823 15.9489C23.7823 15.9451 23.7823 15.9412 23.7823 15.9373Z" fill="${THEME.gray[400]}"/><path d="M23.1143 28.004C23.1205 30.9598 25.5057 33.3541 28.4472 33.3541C31.3886 33.3541 33.7738 30.9598 33.7801 28.004H23.1143Z" fill="${THEME.gray[400]}"/><path d="M13.7846 28.004C13.7846 28.0079 13.7846 28.0117 13.7846 28.0156C13.7908 30.9714 16.1761 33.3657 19.1175 33.3657C22.0589 33.3657 24.4442 30.9714 24.4504 28.0156H13.7846V28.004Z" fill="${THEME.gray[400]}"/><path d="M14.4527 15.9373C14.4527 16.6792 13.8545 17.2806 13.1166 17.2806C12.3786 17.2806 11.7805 16.6792 11.7805 15.9373C11.7805 15.1954 12.3786 14.594 13.1166 14.594C13.8545 14.594 14.4527 15.1954 14.4527 15.9373Z" fill="${THEME.gray[400]}"/><path d="M25.1184 15.2657C25.1184 16.0076 24.5202 16.609 23.7823 16.609C23.0444 16.609 22.4462 16.0076 22.4462 15.2657C22.4462 14.5238 23.0444 13.9224 23.7823 13.9224C24.5202 13.9224 25.1184 14.5238 25.1184 15.2657Z" fill="${THEME.gray[400]}"/><path d="M35.7842 15.9373C35.7842 16.6792 35.186 17.2806 34.4481 17.2806C33.7102 17.2806 33.112 16.6792 33.112 15.9373C33.112 15.1954 33.7102 14.594 34.4481 14.594C35.186 14.594 35.7842 15.1954 35.7842 15.9373Z" fill="${THEME.gray[400]}"/></svg>`,bubbleColor:THEME.gray[100],textColor:THEME.gray[900],side:"right"},xwy:{avatar:`<svg width="28" height="28" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.821 17.5305C10.709 18.17 9.68345 19.4423 9.22624 20.1359C9.11159 20.3099 9.21615 20.5428 9.42038 20.5839L12.67 21.2381C12.8291 21.2702 12.9328 21.4275 12.9084 21.5879C11.3004 32.1653 21.5275 36.7547 28.6638 33.0597C28.7443 33.018 28.8408 33.0139 28.9245 33.0487C32.8032 34.6598 35.967 34.5662 37.8217 34.3099C38.131 34.2671 38.1505 33.841 37.855 33.7401C29.1343 30.7633 26.0152 24.5245 25.5144 18.8022C25.3835 17.3066 23.8172 13.2016 19.2675 13.0058C15.7934 12.8563 13.6137 15.6103 13.0319 17.325C12.9986 17.4231 12.9201 17.5004 12.821 17.5305Z" fill="${THEME.yyPink[100]}"/><circle cx="17.6178" cy="18.2688" r="0.995689" fill="white"/></svg>`,bubbleColor:THEME.yyPink[50],textColor:THEME.gray[900],side:"left"},"小乌鸦":{avatar:`<svg width="28" height="28" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.821 17.5305C10.709 18.17 9.68345 19.4423 9.22624 20.1359C9.11159 20.3099 9.21615 20.5428 9.42038 20.5839L12.67 21.2381C12.8291 21.2702 12.9328 21.4275 12.9084 21.5879C11.3004 32.1653 21.5275 36.7547 28.6638 33.0597C28.7443 33.018 28.8408 33.0139 28.9245 33.0487C32.8032 34.6598 35.967 34.5662 37.8217 34.3099C38.131 34.2671 38.1505 33.841 37.855 33.7401C29.1343 30.7633 26.0152 24.5245 25.5144 18.8022C25.3835 17.3066 23.8172 13.2016 19.2675 13.0058C15.7934 12.8563 13.6137 15.6103 13.0319 17.325C12.9986 17.4231 12.9201 17.5004 12.821 17.5305Z" fill="${THEME.yyPink[100]}"/><circle cx="17.6178" cy="18.2688" r="0.995689" fill="white"/></svg>`,bubbleColor:THEME.yyPink[50],textColor:THEME.gray[900],side:"left"}},FIRST_CHAR_AVATAR_AUTHORS=new Set(["小乌鸦"]);class BBMsgHistory extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render(),this._setupResizeObserver()}disconnectedCallback(){this._resizeObserver?.disconnect()}_setupResizeObserver(){"ResizeObserver"in window&&(this._resizeObserver=new ResizeObserver(()=>{this._adjustLayout()}),this._resizeObserver.observe(this))}_adjustLayout(){}parseMessages(){const n=this.textContent||"",e=[];for(const t of n.split("\n")){const n=t.trim();if(!n)continue;const r=n.indexOf(":");if(r<=0)continue;const o=n.slice(0,r).trim(),a=n.slice(r+1).trim();o&&a&&e.push({author:o,text:a})}return e}getAuthorConfig(n){if(FIRST_CHAR_AVATAR_AUTHORS.has(n)){const e=AUTHOR_CONFIG[n],t=n.charAt(0);return{...e||{bubbleColor:THEME.gray[50],textColor:THEME.gray[900],side:"left"},avatar:this._generateLetterAvatar(t),isCustomAvatar:!1}}if(AUTHOR_CONFIG[n])return{...AUTHOR_CONFIG[n],isCustomAvatar:!0};for(const[e,t]of Object.entries(AUTHOR_CONFIG))if(n.includes(e)||e.includes(n))return{...t,isCustomAvatar:!0};const e=n.charAt(0).toUpperCase();return{avatar:this._generateLetterAvatar(e),bubbleColor:THEME.gray[50],textColor:THEME.gray[900],side:"left",isCustomAvatar:!1}}_generateLetterAvatar(n){return`<div style="\n width: 100%; \n height: 100%; \n display: flex; \n align-items: center; \n justify-content: center; \n background: #ffffff; \n color: ${THEME.gray[600]}; \n font-size: 14px; \n font-weight: 600;\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;\n ">${n}</div>`}escapeHtml(n){const e=document.createElement("div");return e.textContent=n,e.innerHTML}render(){const n=this.parseMessages();if(0===n.length)return void this._renderEmpty();let e="";const t=n.map(({author:n,text:t})=>{const r=this.getAuthorConfig(n),o=n!==e;e=n;const a=o,i=r.side,s=!o,l=`\n <div class="avatar-wrapper ${a?"":"avatar-wrapper--hidden"}" \n data-author="${this.escapeHtml(n)}">\n <div class="avatar">${r.avatar}</div>\n <div class="avatar-tooltip">${this.escapeHtml(n)}</div>\n </div>\n `;return`\n <div class="msg-row msg-row--${i} ${s?"msg-row--subsequent":"msg-row--new-author"}">\n ${"left"===i?l:""}\n \n <div class="msg-content">\n <div class="msg-bubble msg-bubble--${i}" \n style="background-color: ${r.bubbleColor}; color: ${r.textColor};">\n ${this.escapeHtml(t)}\n </div>\n </div>\n \n ${"right"===i?l:""}\n </div>\n `}).join("");this.shadowRoot.innerHTML=`\n <style>\n :host {\n display: block;\n font-family: "PT Sans", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,\n "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,\n "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",\n "Noto Color Emoji";\n --bb-bg-color: ${THEME.gray[50]};\n }\n\n .history {\n max-width: 640px;\n margin: 0 auto;\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n max-height: 600px;\n overflow-y: auto;\n scroll-behavior: smooth;\n background-color: transparent;\n border-radius: 0.5rem;\n }\n\n .history::-webkit-scrollbar {\n width: 0.5rem;\n }\n .history::-webkit-scrollbar-track {\n background: transparent;\n }\n .history::-webkit-scrollbar-thumb {\n background-color: ${THEME.gray[300]};\n border-radius: 0.25rem;\n }\n\n /* 消息行布局 */\n .msg-row {\n display: flex;\n align-items: flex-end;\n gap: 0.5rem;\n max-width: 80%;\n animation: fadeIn 0.2s ease-out;\n }\n\n @keyframes fadeIn {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n .msg-row--left {\n align-self: flex-start;\n margin-right: auto;\n }\n\n .msg-row--right {\n align-self: flex-end;\n margin-left: auto;\n }\n\n .msg-row--subsequent {\n margin-top: 0.125rem;\n }\n\n .msg-row--new-author {\n margin-top: 0.75rem;\n }\n\n .msg-row--new-author:first-child {\n margin-top: 0;\n }\n\n /* 头像容器 */\n .avatar-wrapper {\n position: relative;\n flex-shrink: 0;\n width: 1.75rem;\n height: 1.75rem;\n border-radius: 50%;\n overflow: hidden;\n background: #ffffff;\n cursor: help;\n transition: transform 0.15s ease;\n }\n\n .avatar-wrapper:hover {\n transform: scale(1.1);\n }\n\n .avatar-wrapper--hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .avatar {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .avatar svg {\n width: 100%;\n height: 100%;\n }\n\n /* 悬浮显示名字 */\n .avatar-tooltip {\n position: absolute;\n bottom: calc(100% + 0.5rem);\n left: 50%;\n transform: translateX(-50%) scale(0.9);\n padding: 0.25rem 0.5rem;\n background: ${THEME.gray[800]};\n color: ${THEME.gray[50]};\n font-size: 0.75rem;\n border-radius: 0.25rem;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition: all 0.15s ease;\n pointer-events: none;\n z-index: 10;\n font-weight: 500;\n letter-spacing: 0.02em;\n }\n\n .avatar-tooltip::after {\n content: '';\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 4px solid transparent;\n border-top-color: ${THEME.gray[800]};\n }\n\n .avatar-wrapper:hover .avatar-tooltip {\n opacity: 1;\n visibility: visible;\n transform: translateX(-50%) scale(1);\n }\n\n /* 消息内容区 */\n .msg-content {\n display: flex;\n flex-direction: column;\n }\n\n .msg-bubble {\n padding: 0.625rem 0.875rem;\n font-size: 0.9375rem;\n line-height: 1.5;\n word-wrap: break-word;\n overflow-wrap: anywhere;\n word-break: break-word;\n border-radius: 1rem;\n }\n\n /* 左侧气泡 */\n .msg-bubble--left {\n border-bottom-left-radius: 0.25rem;\n background-color: ${THEME.gray[200]};\n color: ${THEME.gray[900]};\n }\n\n /* 右侧气泡 */\n .msg-bubble--right {\n border-bottom-right-radius: 0.25rem;\n /* 移除边框 */\n }\n\n /* 空状态 */\n .empty-state {\n text-align: center;\n padding: 2rem;\n color: ${THEME.gray[400]};\n font-size: 0.875rem;\n }\n\n /* 移动端 */\n @media (max-width: 480px) {\n .history {\n max-height: 70vh;\n }\n \n .msg-row {\n max-width: 85%;\n }\n \n .msg-bubble {\n font-size: 0.9375rem;\n padding: 0.5rem 0.75rem;\n }\n \n .avatar-wrapper {\n width: 1.5rem;\n height: 1.5rem;\n }\n }\n\n /* 暗色模式 */\n @media (prefers-color-scheme: dark) {\n :host {\n --bb-bg-color: ${THEME.gray[900]};\n }\n \n .msg-bubble--left {\n background-color: ${THEME.gray[700]} !important;\n color: ${THEME.gray[100]} !important;\n }\n \n .msg-bubble--right {\n background-color: ${THEME.gray[800]} !important;\n color: ${THEME.gray[100]} !important;\n border-color: ${THEME.gray[700]};\n }\n \n .avatar-wrapper {\n background: #ffffff;\n }\n \n .avatar-tooltip {\n background: ${THEME.gray[200]};\n color: ${THEME.gray[900]};\n }\n \n .avatar-tooltip::after {\n border-top-color: ${THEME.gray[200]};\n }\n }\n\n /* 减少动画 */\n @media (prefers-reduced-motion: reduce) {\n .msg-row {\n animation: none;\n }\n }\n </style>\n <div class="history" role="log" aria-live="polite" aria-label="Message history">\n ${t}\n </div>\n `,requestAnimationFrame(()=>{const n=this.shadowRoot.querySelector(".history");n&&(n.scrollTop=n.scrollHeight)})}_renderEmpty(){this.shadowRoot.innerHTML=`\n <style>\n :host { display: block; }\n .empty-state {\n text-align: center;\n padding: 2rem;\n color: ${THEME.gray[400]};\n font-size: 0.875rem;\n font-family: inherit;\n }\n </style>\n <div class="empty-state">No messages</div>\n `}}function initBBMsgHistory(){try{customElements.get("bb-msg-history")||customElements.define("bb-msg-history",BBMsgHistory)}catch(n){console.warn("BBMsgHistory registration failed, falling back to plain text:",n),document.querySelectorAll("bb-msg-history").forEach(n=>{const e=document.createElement("pre");e.style.cssText=`\n background: ${THEME.gray[100]};\n padding: 1rem;\n border-radius: 0.5rem;\n overflow-x: auto;\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;\n font-size: 0.875rem;\n line-height: 1.5;\n color: ${THEME.gray[900]};\n margin: 0;\n white-space: pre-wrap;\n word-wrap: break-word;\n border: 1px solid ${THEME.gray[200]};\n `,e.textContent=n.textContent||"",n.replaceWith(e)})}}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",initBBMsgHistory):initBBMsgHistory();export{BBMsgHistory};
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -103,14 +103,13 @@ class BBMsgHistory extends HTMLElement {
|
|
|
103
103
|
|
|
104
104
|
for (const line of raw.split('\n')) {
|
|
105
105
|
const trimmed = line.trim();
|
|
106
|
-
if (!trimmed
|
|
106
|
+
if (!trimmed) continue;
|
|
107
107
|
|
|
108
|
-
const
|
|
109
|
-
const colonIdx = content.indexOf(':');
|
|
108
|
+
const colonIdx = trimmed.indexOf(':');
|
|
110
109
|
if (colonIdx <= 0) continue;
|
|
111
110
|
|
|
112
|
-
const author =
|
|
113
|
-
const text =
|
|
111
|
+
const author = trimmed.slice(0, colonIdx).trim();
|
|
112
|
+
const text = trimmed.slice(colonIdx + 1).trim();
|
|
114
113
|
|
|
115
114
|
if (author && text) {
|
|
116
115
|
messages.push({ author, text });
|
|
@@ -204,7 +203,7 @@ class BBMsgHistory extends HTMLElement {
|
|
|
204
203
|
`;
|
|
205
204
|
|
|
206
205
|
return `
|
|
207
|
-
<div class="msg-row msg-row--${side} ${isSubsequent ? 'msg-row--subsequent' : ''}">
|
|
206
|
+
<div class="msg-row msg-row--${side} ${isSubsequent ? 'msg-row--subsequent' : 'msg-row--new-author'}">
|
|
208
207
|
${side === 'left' ? avatarHtml : ''}
|
|
209
208
|
|
|
210
209
|
<div class="msg-content">
|
|
@@ -234,7 +233,6 @@ class BBMsgHistory extends HTMLElement {
|
|
|
234
233
|
.history {
|
|
235
234
|
max-width: 640px;
|
|
236
235
|
margin: 0 auto;
|
|
237
|
-
padding: 1rem;
|
|
238
236
|
display: flex;
|
|
239
237
|
flex-direction: column;
|
|
240
238
|
gap: 0.25rem;
|
|
@@ -284,6 +282,14 @@ class BBMsgHistory extends HTMLElement {
|
|
|
284
282
|
margin-top: 0.125rem;
|
|
285
283
|
}
|
|
286
284
|
|
|
285
|
+
.msg-row--new-author {
|
|
286
|
+
margin-top: 0.75rem;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.msg-row--new-author:first-child {
|
|
290
|
+
margin-top: 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
287
293
|
/* 头像容器 */
|
|
288
294
|
.avatar-wrapper {
|
|
289
295
|
position: relative;
|
|
@@ -396,7 +402,6 @@ class BBMsgHistory extends HTMLElement {
|
|
|
396
402
|
/* 移动端 */
|
|
397
403
|
@media (max-width: 480px) {
|
|
398
404
|
.history {
|
|
399
|
-
padding: 0.75rem;
|
|
400
405
|
max-height: 70vh;
|
|
401
406
|
}
|
|
402
407
|
|