communications app, popup lib
This commit is contained in:
		| @@ -13,14 +13,14 @@ $searchInputHeight: 26px; | |||||||
| $searchIconSize: 13px; | $searchIconSize: 13px; | ||||||
| $listMaxHeight: 210px; | $listMaxHeight: 210px; | ||||||
|  |  | ||||||
| .dropdown-wrapper { | .drop-wrapper { | ||||||
|     display: inline-block; |     display: inline-block; | ||||||
|     border: none; |     border: none; | ||||||
|     border-radius: unset; |     border-radius: unset; | ||||||
|     user-select: none; |     user-select: none; | ||||||
|     position: relative; |     position: relative; | ||||||
|  |  | ||||||
|     >.dropdown-header { |     >.drop-header { | ||||||
|         border: 1px solid $borderColor; |         border: 1px solid $borderColor; | ||||||
|         border-radius: $borderRadius; |         border-radius: $borderRadius; | ||||||
|         background-color: $bgColor; |         background-color: $bgColor; | ||||||
| @@ -39,7 +39,7 @@ $listMaxHeight: 210px; | |||||||
|             outline: none; |             outline: none; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /*>.dropdown-select-container { |         /*>.drop-select-container { | ||||||
|             flex: 1 1 auto; |             flex: 1 1 auto; | ||||||
|             overflow-x: auto; |             overflow-x: auto; | ||||||
|             white-space: nowrap; |             white-space: nowrap; | ||||||
| @@ -104,7 +104,7 @@ $listMaxHeight: 210px; | |||||||
|             } |             } | ||||||
|         }*/ |         }*/ | ||||||
|  |  | ||||||
|         >.dropdown-text { |         >.drop-text { | ||||||
|             flex: 1 1 auto; |             flex: 1 1 auto; | ||||||
|             cursor: pointer; |             cursor: pointer; | ||||||
|             font-size: $mediumSize; |             font-size: $mediumSize; | ||||||
| @@ -117,7 +117,7 @@ $listMaxHeight: 210px; | |||||||
|             white-space: nowrap; |             white-space: nowrap; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         >input.dropdown-text { |         >input.drop-text { | ||||||
|             cursor: initial; |             cursor: initial; | ||||||
|  |  | ||||||
|             &::placeholder { |             &::placeholder { | ||||||
| @@ -126,7 +126,7 @@ $listMaxHeight: 210px; | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         >.dropdown-caret { |         >.drop-caret { | ||||||
|             flex: 0 0 auto; |             flex: 0 0 auto; | ||||||
|             width: $caretWidth; |             width: $caretWidth; | ||||||
|             display: flex; |             display: flex; | ||||||
| @@ -154,14 +154,14 @@ $listMaxHeight: 210px; | |||||||
|                 // box-shadow: none; |                 // box-shadow: none; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             >.dropdown-text, |             >.drop-text, | ||||||
|             >.dropdown-caret { |             >.drop-caret { | ||||||
|                 cursor: default; |                 cursor: default; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     >.dropdown-box { |     >.drop-box { | ||||||
|         position: absolute; |         position: absolute; | ||||||
|         visibility: hidden; |         visibility: hidden; | ||||||
|         opacity: 0; |         opacity: 0; | ||||||
| @@ -192,7 +192,7 @@ $listMaxHeight: 210px; | |||||||
|             transform: scaleY(1); |             transform: scaleY(1); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         >.dropdown-search { |         >.drop-search { | ||||||
|             box-sizing: border-box; |             box-sizing: border-box; | ||||||
|             height: $searchBarHeight; |             height: $searchBarHeight; | ||||||
|             line-height: $searchBarHeight; |             line-height: $searchBarHeight; | ||||||
| @@ -235,7 +235,7 @@ $listMaxHeight: 210px; | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         >.dropdown-list { |         >.drop-list { | ||||||
|             margin: 0; |             margin: 0; | ||||||
|             padding: 0; |             padding: 0; | ||||||
|             list-style: none; |             list-style: none; | ||||||
|   | |||||||
| @@ -59,23 +59,4 @@ $boxDisabledColor: #d9d9d9; | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /* |  | ||||||
|     &.disabled { |  | ||||||
|         .check-box-inner { |  | ||||||
|             border-color: $boxDisabledColor; |  | ||||||
|             cursor: default; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         >input:checked+.check-box-inner { |  | ||||||
|             border-color: $boxDisabledColor; |  | ||||||
|             background-color: $boxDisabledColor; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         >span { |  | ||||||
|             color: $boxDisabledColor; |  | ||||||
|             cursor: default; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     */ |  | ||||||
| } | } | ||||||
| @@ -63,8 +63,8 @@ | |||||||
|  |  | ||||||
|     &, |     &, | ||||||
|     input[type="text"], |     input[type="text"], | ||||||
|     .dropdown-wrapper>.dropdown-header>.dropdown-text, |     .drop-wrapper>.drop-header>.drop-text, | ||||||
|     .dropdown-wrapper>.dropdown-box>.dropdown-list { |     .drop-wrapper>.drop-box>.drop-list { | ||||||
|         font-size: var(--font-size); |         font-size: var(--font-size); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -268,22 +268,22 @@ | |||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     .dropdown-wrapper { |                     .drop-wrapper { | ||||||
|                         height: calc(var(--line-height) + 2px); |                         height: calc(var(--line-height) + 2px); | ||||||
|                         width: 100%; |                         width: 100%; | ||||||
|                         display: flex; |                         display: flex; | ||||||
|                         flex-direction: column; |                         flex-direction: column; | ||||||
|  |  | ||||||
|                         >.dropdown-header { |                         >.drop-header { | ||||||
|                             border: none; |                             border: none; | ||||||
|                             height: 100%; |                             height: 100%; | ||||||
|  |  | ||||||
|                             >.dropdown-text { |                             >.drop-text { | ||||||
|                                 padding: var(--spacing-cell); |                                 padding: var(--spacing-cell); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|                         >.dropdown-box { |                         >.drop-box { | ||||||
|                             top: calc(var(--line-height) + 2px); |                             top: calc(var(--line-height) + 2px); | ||||||
|  |  | ||||||
|                             &.slide-up { |                             &.slide-up { | ||||||
|   | |||||||
							
								
								
									
										94
									
								
								css/popup.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								css/popup.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | @import './functions/func.scss'; | ||||||
|  | @import './variables/definition.scss'; | ||||||
|  |  | ||||||
|  | $headerLineHeight: 24px; | ||||||
|  | $buttonHeight: 36px; | ||||||
|  |  | ||||||
|  | .popup-mask { | ||||||
|  |     position: fixed; | ||||||
|  |     @include inset(0, 0, 0, 0); | ||||||
|  |     background-color: rgba(0 0 0 /20%); | ||||||
|  |     display: flex; | ||||||
|  |     justify-content: center; | ||||||
|  |     align-items: center; | ||||||
|  |     opacity: 0; | ||||||
|  |     z-index: 200; | ||||||
|  |     transition: opacity .12s ease; | ||||||
|  |  | ||||||
|  |     &.active .popup-container { | ||||||
|  |         transform: scale(1.1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .popup-container { | ||||||
|  |         min-width: 400px; | ||||||
|  |         max-width: 800px; | ||||||
|  |         background-color: $bgColor; | ||||||
|  |         border-radius: 2px; | ||||||
|  |         box-shadow: 0 2px 8px rgba(0 0 0 /11%); | ||||||
|  |         transition: all .12s ease; | ||||||
|  |         position: absolute; | ||||||
|  |         display: flex; | ||||||
|  |         flex-direction: column; | ||||||
|  |  | ||||||
|  |         .popup-header { | ||||||
|  |             flex: 0 0 auto; | ||||||
|  |             padding: 10px 12px 6px; | ||||||
|  |             line-height: $headerLineHeight; | ||||||
|  |             user-select: none; | ||||||
|  |             background-color: var(--title-bg-color); | ||||||
|  |             color: var(--title-color); | ||||||
|  |             display: flex; | ||||||
|  |  | ||||||
|  |             >div { | ||||||
|  |                 flex: 1 1 auto; | ||||||
|  |                 font-size: 1rem; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             >svg { | ||||||
|  |                 flex: 0 0 auto; | ||||||
|  |                 width: $headerLineHeight; | ||||||
|  |                 height: $headerLineHeight; | ||||||
|  |                 padding: 4px; | ||||||
|  |                 cursor: pointer; | ||||||
|  |                 box-sizing: border-box; | ||||||
|  |  | ||||||
|  |                 &:hover { | ||||||
|  |                     opacity: .8; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .popup-body { | ||||||
|  |             margin: 6px 10px; | ||||||
|  |             flex: 1 1 auto; | ||||||
|  |             line-height: $headerLineHeight; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .popup-footer { | ||||||
|  |             flex: 0 0 auto; | ||||||
|  |             display: flex; | ||||||
|  |             align-items: center; | ||||||
|  |             justify-content: flex-end; | ||||||
|  |             padding: 4px 10px 16px 2px; | ||||||
|  |  | ||||||
|  |             .popup-button { | ||||||
|  |                 margin-left: 8px; | ||||||
|  |                 border: none; | ||||||
|  |                 height: $buttonHeight; | ||||||
|  |                 padding: 0 8px; | ||||||
|  |                 min-width: 60px; | ||||||
|  |                 cursor: pointer; | ||||||
|  |                 background-color: var(--title-bg-color); | ||||||
|  |  | ||||||
|  |                 &:hover { | ||||||
|  |                     opacity: .8; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 &:focus, | ||||||
|  |                 &:focus-visible { | ||||||
|  |                     outline: none; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,5 +1,7 @@ | |||||||
| import CustomerCommunication from "./app/communications/customer"; | import CustomerCommunication from "./app/communications/customer"; | ||||||
|  | import InternalComment from "./app/communications/internal"; | ||||||
|  |  | ||||||
| export { | export { | ||||||
|     CustomerCommunication |     CustomerCommunication, | ||||||
|  |     InternalComment | ||||||
| } | } | ||||||
| @@ -1,11 +1,13 @@ | |||||||
| import "./style.scss"; | import "./style.scss"; | ||||||
| import { createElement, createElementInit } from "../../functions"; | import { createElement } from "../../functions"; | ||||||
| import { r } from "../../utility/lgres"; | import { r } from "../../utility/lgres"; | ||||||
|  | import { nullOrEmpty } from "../../utility/strings"; | ||||||
| import { formatUrl, isEmail, isPhone } from "../../utility"; | import { formatUrl, isEmail, isPhone } from "../../utility"; | ||||||
| import { setTooltip } from "../../ui/tooltip"; | import { setTooltip } from "../../ui/tooltip"; | ||||||
| import { createIcon } from "../../ui/icon"; | import { createIcon } from "../../ui/icon"; | ||||||
| import { createCheckbox } from "../../ui/checkbox"; | import { createCheckbox } from "../../ui/checkbox"; | ||||||
| import { createBox } from "./lib"; | import { createBox } from "./lib"; | ||||||
|  | import { createPopup } from "../../ui/popup"; | ||||||
|  |  | ||||||
| class CustomerCommunication { | class CustomerCommunication { | ||||||
|     #container; |     #container; | ||||||
| @@ -23,123 +25,223 @@ class CustomerCommunication { | |||||||
|     get autoUpdatesEnabled() { return this.#autoUpdates?.disabled !== true } |     get autoUpdatesEnabled() { return this.#autoUpdates?.disabled !== true } | ||||||
|     set autoUpdatesEnabled(flag) { |     set autoUpdatesEnabled(flag) { | ||||||
|         const element = this.#autoUpdates; |         const element = this.#autoUpdates; | ||||||
|         element != null && (element.disabled = flag === false); |         if (element == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         if (flag === false) { | ||||||
|  |             element.disabled = true; | ||||||
|  |             element.parentElement?.classList?.add('disabled'); | ||||||
|  |         } else { | ||||||
|  |             element.disabled = false; | ||||||
|  |             element.parentElement?.classList?.remove('disabled'); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     get autoUpdates() { return this.#autoUpdates?.checked } |     get autoUpdates() { return this.#autoUpdates?.checked } | ||||||
|     set autoUpdates(flag) { |     set autoUpdates(flag) { | ||||||
|         const element = this.#autoUpdates; |         const element = this.#autoUpdates; | ||||||
|         element != null && (element.checked = flag); |         if (element == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         element.checked = flag; | ||||||
|  |         element.dispatchEvent(new Event('change')); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     get #statusLink() { return this.#container.querySelector('.check-status-link') } |     get #statusLink() { return this.#container.querySelector('.check-status-link') } | ||||||
|     get statusLinkEnabled() { return this.#statusLink?.disabled !== true } |     get statusLinkEnabled() { return this.#statusLink?.disabled !== true } | ||||||
|     set statusLinkEnabled(flag) { |     set statusLinkEnabled(flag) { | ||||||
|         const element = this.#statusLink; |         const element = this.#statusLink; | ||||||
|         element != null && (element.disabled = flag === false); |         if (element == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         if (flag === false) { | ||||||
|  |             element.disabled = true; | ||||||
|  |             element.parentElement?.classList?.add('disabled'); | ||||||
|  |         } else { | ||||||
|  |             element.disabled = false; | ||||||
|  |             element.parentElement?.classList?.remove('disabled'); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     get statusLink() { return this.#statusLink?.checked } |     get statusLink() { return this.#statusLink?.checked } | ||||||
|     set statusLink(flag) { |     set statusLink(flag) { | ||||||
|         const element = this.#statusLink; |         const element = this.#statusLink; | ||||||
|         element != null && (element.checked = flag); |         if (element == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         element.checked = flag; | ||||||
|  |         element.dispatchEvent(new Event('change')); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     get text() { return this.#enter?.value } |     get text() { return this.#enter?.value } | ||||||
|     set text(s) { |     set text(s) { | ||||||
|         const element = this.#enter; |         const element = this.#enter; | ||||||
|         element != null && (element.value = s); |         if (element != null) { | ||||||
|  |             element.value = s | ||||||
|  |             s = String(nullOrEmpty(s) ? 0 : val.length) + '/3000'; | ||||||
|  |             this.#container.querySelector('.message-bar .prompt-count').innerText = s; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #createContactItem(c) { | ||||||
|  |         if (c.OptOut || c.OptOut_BC) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         const mp = String(c.MobilePhone).trim(); | ||||||
|  |         const email = String(c.Email).trim(); | ||||||
|  |         if (c.ContactPreference === '0' && !isPhone(mp) || | ||||||
|  |             c.ContactPreference === '1' && !isEmail(email)) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         const to = c.ContactPreference === '0' ? mp : email; | ||||||
|  |         return createElement('div', 'contact-item', | ||||||
|  |             createIcon('fa-light', c.ContactPreference === '0' ? 'comment-lines' : 'envelope'), | ||||||
|  |             setTooltip(createElement('span', span => { | ||||||
|  |                 span.dataset.to = to; | ||||||
|  |                 span.dataset.name = c.Name; | ||||||
|  |                 span.innerText = to; | ||||||
|  |             }), to, true) | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     get contacts() { |     get contacts() { | ||||||
|         return [...this.#contacts.children].map(el => { |         return [...this.#contacts.children].map(el => { | ||||||
|             const span = el.querySelector('span'); |             const span = el.querySelector('span'); | ||||||
|             return { 'Key': span.innerText, 'Value': span.dataset.name }; |             return { 'Key': span.dataset.to, 'Value': span.dataset.name }; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     set contacts(contacts) { |     set contacts(contacts) { | ||||||
|         this.#contacts.replaceChildren(); |         this.#contacts.replaceChildren(); | ||||||
|         if (contacts?.length > 0) { |         if (contacts?.length > 0) { | ||||||
|             for (let c of contacts) { |             for (let c of contacts) { | ||||||
|                 if (c.OptOut || c.OptOut_BC) { |                 const item = this.#createContactItem(c); | ||||||
|                     continue; |                 if (item != null) { | ||||||
|  |                     this.#contacts.appendChild(item); | ||||||
|                 } |                 } | ||||||
|                 const mp = String(c.MobilePhone).trim(); |  | ||||||
|                 const email = String(c.Email).trim(); |  | ||||||
|                 if (c.ContactPreference === '0' && !isPhone(mp) || |  | ||||||
|                     c.ContactPreference === '1' && !isEmail(email)) { |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|                 const to = c.ContactPreference === '0' ? mp : email; |  | ||||||
|                 this.#contacts.appendChild( |  | ||||||
|                     createElement('div', 'contact-item', |  | ||||||
|                         createIcon('fa-light', c.ContactPreference === '0' ? 'comment-lines' : 'envelope'), |  | ||||||
|                         createElementInit('span', span => { |  | ||||||
|                             span.dataset.name = c.Name; |  | ||||||
|                             span.innerText = to; |  | ||||||
|                         }) |  | ||||||
|                     ) |  | ||||||
|                 ) |  | ||||||
|             } |             } | ||||||
|  |             this.#message.scrollTop = this.#message.scrollHeight | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param {boolean} flag | ||||||
|  |      */ | ||||||
|  |     set readonly(flag) { | ||||||
|  |         this.#option.readonly = flag; | ||||||
|  |         if (this.#container == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         this.#container.querySelector('.button-edit-contacts').style.display = flag === true ? 'none' : ''; | ||||||
|  |         this.#container.querySelector('.button-edit-followers').style.display = flag === true ? 'none' : ''; | ||||||
|  |         this.#enter.disabled = flag === true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     get followers() { | ||||||
|  |         return [...this.#followers.children].map(el => { | ||||||
|  |             const span = el.querySelector('span'); | ||||||
|  |             return { 'Key': span.dataset.to, 'Value': span.dataset.name }; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     set followers(followers) { | ||||||
|  |         this.#followers.replaceChildren(); | ||||||
|  |         if (followers?.length > 0) { | ||||||
|  |             this.#container.querySelector('.follower-bar').style.display = ''; | ||||||
|  |             for (let f of followers) { | ||||||
|  |                 const item = this.#createContactItem(f); | ||||||
|  |                 if (item != null) { | ||||||
|  |                     this.#followers.appendChild(item); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             this.#container.querySelector('.follower-bar').style.display = 'none'; | ||||||
|  |         } | ||||||
|  |         this.#message.scrollTop = this.#message.scrollHeight | ||||||
|  |     } | ||||||
|  |  | ||||||
|     create() { |     create() { | ||||||
|         // functions |         // functions | ||||||
|         const checkAutoUpdate = createCheckbox({ |         const checkAutoUpdate = createCheckbox({ | ||||||
|             className: 'check-auto-update', |             className: 'check-auto-update', | ||||||
|             checked: this.#option.autoUpdates?.checked, |             checked: this.#option.autoUpdates, | ||||||
|             enabled: this.#option.autoUpdates?.enabled, |  | ||||||
|             checkedNode: createIcon('fa-regular', 'redo-alt'), |             checkedNode: createIcon('fa-regular', 'redo-alt'), | ||||||
|             uncheckedNode: createIcon('fa-regular', 'ban'), |             uncheckedNode: createIcon('fa-regular', 'ban'), | ||||||
|             onchange: () => { |             onchange: function () { | ||||||
|                 setTooltip(checkAutoUpdate, this.checked ? |                 setTooltip(checkAutoUpdate, this.checked ? | ||||||
|                     r('autoUpdateEnabled', 'Auto Updates Enabled') : |                     r('autoUpdateEnabled', 'Auto Updates Enabled') : | ||||||
|                     r('autoUpdateDisabled', 'Auto Updates Disabled')); |                     r('autoUpdateDisabled', 'Auto Updates Disabled')); | ||||||
|                 if (typeof this.#option.autoUpdates?.onchanged === 'function') { |  | ||||||
|                     this.#option.autoUpdates.onchanged(this.checked); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         const checkLink = createCheckbox({ |         const checkLink = createCheckbox({ | ||||||
|             className: 'check-status-link', |             className: 'check-status-link', | ||||||
|             checked: this.#option.statusLink?.checked, |             checked: this.#option.statusLink, | ||||||
|             enabled: this.#option.statusLink?.enabled, |  | ||||||
|             checkedNode: createIcon('fa-regular', 'link'), |             checkedNode: createIcon('fa-regular', 'link'), | ||||||
|             uncheckedNode: createIcon('fa-regular', 'unlink'), |             uncheckedNode: createIcon('fa-regular', 'unlink'), | ||||||
|             onchange: () => { |             onchange: function () { | ||||||
|                 setTooltip(checkLink, this.checked ? |                 setTooltip(checkLink, this.checked ? | ||||||
|                     r('statusLinkIncluded', 'Status Link Included') : |                     r('statusLinkIncluded', 'Status Link Included') : | ||||||
|                     r('statusLinkExcluded', 'Status Link Excluded')); |                     r('statusLinkExcluded', 'Status Link Excluded')); | ||||||
|                 if (typeof this.#option.statusLink?.onchanged === 'function') { |  | ||||||
|                     this.#option.statusLink.onchanged(this.checked); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         const container = createBox( |         const container = createBox( | ||||||
|             createElement('div', null, |             createElement('div', null, | ||||||
|                 createElementInit('div', div => div.innerText = r('messages', 'Customer Communication')), |                 createElement('div', div => div.innerText = r('messages', 'Customer Communication')), | ||||||
|                 createElementInit('div', div => div.innerText = consts.user?.companyName)), |                 createElement('div', div => { | ||||||
|  |                     div.className = 'title-company'; | ||||||
|  |                     if (nullOrEmpty(this.#option.companyName)) { | ||||||
|  |                         div.style.display = 'none'; | ||||||
|  |                     } else { | ||||||
|  |                         div.innerText = this.#option.companyName; | ||||||
|  |                     } | ||||||
|  |                 }) | ||||||
|  |             ), | ||||||
|             [ |             [ | ||||||
|                 setTooltip(checkAutoUpdate, r('autoUpdateDisabled', 'Auto Updates Disabled')), |                 setTooltip(checkAutoUpdate, r('autoUpdateEnabled', 'Auto Updates Enabled')), | ||||||
|                 setTooltip(checkLink, r('statusLinkExcluded', 'Status Link Excluded')) |                 setTooltip(checkLink, r('statusLinkExcluded', 'Status Link Excluded')) | ||||||
|             ] |             ] | ||||||
|         ); |         ); | ||||||
|         // contacts |         // contacts | ||||||
|  |         const readonly = this.#option.readonly; | ||||||
|         const contacts = createElement('div'); |         const contacts = createElement('div'); | ||||||
|         container.append( |         container.append( | ||||||
|             createElement('div', 'contact-bar', |             createElement('div', 'contact-bar', | ||||||
|                 createIcon('fa-solid', 'user-circle', { |                 createElement('div', 'bar-icon', | ||||||
|                     'fill': 'lightgray', |                     createIcon('fa-solid', 'user-circle', { | ||||||
|                     'flex': '0 0 auto' |                         'fill': 'lightgray' | ||||||
|                 }), |                     }) | ||||||
|                 createElementInit('div', div => div.style.flex = '1 1 auto', |                 ), | ||||||
|  |                 createElement('div', 'bar-list', | ||||||
|                     contacts, |                     contacts, | ||||||
|                     createElementInit('button', button => { |                     createElement('button', button => { | ||||||
|                         button.className = 'roundbtn'; |                         button.className = 'roundbtn button-edit-contacts'; | ||||||
|                         button.style.backgroundColor = 'rgb(1, 199, 172)'; |                         button.style.backgroundColor = 'rgb(1, 199, 172)'; | ||||||
|  |                         if (readonly === true) { | ||||||
|  |                             button.style.display = 'none'; | ||||||
|  |                         } | ||||||
|                         button.appendChild(createIcon('fa-solid', 'user-edit')); |                         button.appendChild(createIcon('fa-solid', 'user-edit')); | ||||||
|                         setTooltip(button, r('editContacts', 'Edit Contacts')); |                         setTooltip(button, r('editContacts', 'Edit Contacts')); | ||||||
|                         button.addEventListener('click', () => { |                         button.addEventListener('click', () => { | ||||||
|                             // TODO: |                             // TODO: | ||||||
|  |                             const pop = createPopup( | ||||||
|  |                                 createElement('div', div => { | ||||||
|  |                                     div.style.display = 'flex'; | ||||||
|  |                                     div.append( | ||||||
|  |                                         createElement('span', span => { | ||||||
|  |                                             span.style.flex = '1 1 auto'; | ||||||
|  |                                             span.innerText = r('editContacts', 'Edit Contacts'); | ||||||
|  |                                         }), | ||||||
|  |                                         createElement('button', button => { | ||||||
|  |                                             button.style.flex = '0 0 auto'; | ||||||
|  |                                             button.className = 'roundbtn button-add-contact'; | ||||||
|  |                                             button.backgroundColor = 'rgb(1, 199, 172)'; | ||||||
|  |                                             button.appendChild(createIcon('fa-regular', 'user')); | ||||||
|  |                                             setTooltip(button, r('addContact', 'Add Contact')) | ||||||
|  |                                         }) | ||||||
|  |                                     ) | ||||||
|  |                                 }), | ||||||
|  |                                 createElement('div', div => { | ||||||
|  |                                     div.style.height = '300px'; | ||||||
|  |                                     div.innerText = 'Contacts from Customer Record'; | ||||||
|  |                                 })); | ||||||
|  |                             container.append(pop); | ||||||
|  |                             setTimeout(() => pop.style.opacity = 1, 0); | ||||||
|                         }); |                         }); | ||||||
|                     }) |                     }) | ||||||
|                 ) |                 ) | ||||||
| @@ -149,20 +251,27 @@ class CustomerCommunication { | |||||||
|         // followers |         // followers | ||||||
|         const followers = createElement('div'); |         const followers = createElement('div'); | ||||||
|         container.append( |         container.append( | ||||||
|             createElement('div', 'contact-bar follower-bar', |             createElement('div', div => { | ||||||
|                 createIcon('fa-solid', 'user-tag', { |                 div.className = 'contact-bar follower-bar'; | ||||||
|                     'fill': '#fff', |                 div.style.display = 'none'; | ||||||
|                     'background-color': 'lightgray', |             }, | ||||||
|                     'box-sizing': 'border-box', |                 setTooltip(createElement('div', 'bar-icon', | ||||||
|                     'border-radius': '15px', |                     createIcon('fa-solid', 'user-tag', { | ||||||
|                     'padding': '4px', |                         'fill': '#fff', | ||||||
|                     'flex': '0 0 auto' |                         'background-color': 'lightgray', | ||||||
|                 }), |                         'box-sizing': 'border-box', | ||||||
|                 createElementInit('div', div => div.style.flex = '1 1 auto', |                         'border-radius': '15px', | ||||||
|  |                         'padding': '4px' | ||||||
|  |                     }) | ||||||
|  |                 ), r('copied', 'Copied')), | ||||||
|  |                 createElement('div', 'bar-list', | ||||||
|                     followers, |                     followers, | ||||||
|                     createElementInit('button', button => { |                     createElement('button', button => { | ||||||
|                         button.className = 'roundbtn'; |                         button.className = 'roundbtn button-edit-followers'; | ||||||
|                         button.style.backgroundColor = 'rgb(48, 107, 255)'; |                         button.style.backgroundColor = 'rgb(48, 107, 255)'; | ||||||
|  |                         if (readonly === true) { | ||||||
|  |                             button.style.display = 'none'; | ||||||
|  |                         } | ||||||
|                         button.appendChild(createIcon('fa-solid', 'pen')); |                         button.appendChild(createIcon('fa-solid', 'pen')); | ||||||
|                         setTooltip(button, r('editFollower', 'Edit Followers')); |                         setTooltip(button, r('editFollower', 'Edit Followers')); | ||||||
|                         button.addEventListener('click', () => { |                         button.addEventListener('click', () => { | ||||||
| @@ -175,20 +284,31 @@ class CustomerCommunication { | |||||||
|         this.#followers = followers; |         this.#followers = followers; | ||||||
|         // enter box |         // enter box | ||||||
|         const enter = createElement('textarea'); |         const enter = createElement('textarea'); | ||||||
|         enter.placeholder = r('typeComment', 'Enter Message Here'); |         enter.placeholder = r('typeMessage', 'Enter Message Here'); | ||||||
|         enter.maxLength = 3000; |         enter.maxLength = 3000; | ||||||
|  |         if (readonly === true) { | ||||||
|  |             enter.disabled = true; | ||||||
|  |         } | ||||||
|  |         enter.addEventListener('input', () => { | ||||||
|  |             const val = this.#enter.value; | ||||||
|  |             const s = String(nullOrEmpty(val) ? 0 : val.length) + '/3000'; | ||||||
|  |             this.#container.querySelector('.message-bar .prompt-count').innerText = s; | ||||||
|  |         }); | ||||||
|         this.#enter = enter; |         this.#enter = enter; | ||||||
|         container.appendChild( |         container.appendChild( | ||||||
|             createElement('div', 'message-bar', |             createElement('div', 'message-bar', | ||||||
|                 enter, |                 enter, | ||||||
|                 createElementInit('div', div => div.style.textAlign = 'right', |                 createElement('div', div => div.style.textAlign = 'right', | ||||||
|                     createElementInit('button', button => { |                     createElement('div', 'prompt-count'), | ||||||
|                         button.className = 'roundbtn'; |                     createElement('button', button => { | ||||||
|  |                         button.className = 'roundbtn button-send-message'; | ||||||
|                         button.style.backgroundColor = 'rgb(19, 150, 204)'; |                         button.style.backgroundColor = 'rgb(19, 150, 204)'; | ||||||
|                         button.appendChild(createIcon('fa-solid', 'paper-plane')); |                         button.appendChild(createIcon('fa-solid', 'paper-plane')); | ||||||
|                         setTooltip(button, r('sendMessage', 'Send Message')); |                         setTooltip(button, r('sendMessage', 'Send Message')); | ||||||
|                         button.addEventListener('click', () => { |                         button.addEventListener('click', () => { | ||||||
|                             // TODO: Add text |                             if (typeof this.#option.onAddMessage === 'function') { | ||||||
|  |                                 this.#option.onAddMessage(this.#enter.value); | ||||||
|  |                             } | ||||||
|                         }) |                         }) | ||||||
|                     }) |                     }) | ||||||
|                 ) |                 ) | ||||||
| @@ -240,7 +360,7 @@ class CustomerCommunication { | |||||||
|                 if (sendto !== '') { |                 if (sendto !== '') { | ||||||
|                     sendto = r('sendToColon', 'Send To :') + `\n${sendto}`; |                     sendto = r('sendToColon', 'Send To :') + `\n${sendto}`; | ||||||
|                 } |                 } | ||||||
|                 div.appendChild(createElementInit('div', div => { |                 div.appendChild(createElement('div', div => { | ||||||
|                     div.className = 'item-poster'; |                     div.className = 'item-poster'; | ||||||
|                     div.innerText = name; |                     div.innerText = name; | ||||||
|                     if (!comm.IsReply && sendto?.length > 0) { |                     if (!comm.IsReply && sendto?.length > 0) { | ||||||
| @@ -248,14 +368,17 @@ class CustomerCommunication { | |||||||
|                     } |                     } | ||||||
|                 })); |                 })); | ||||||
|                 const content = createElement('div', 'item-content'); |                 const content = createElement('div', 'item-content'); | ||||||
|                 if (/https?:\/\//i.test(comm.Message)) { |                 content.appendChild(createElement('span', span => { | ||||||
|                     content.innerHTML = formatUrl(comm.Message); |                     if (/https?:\/\//i.test(comm.Message)) { | ||||||
|                 } else { |                         span.innerHTML = formatUrl(comm.Message); | ||||||
|                     content.innerText = comm.Message; |                     } else { | ||||||
|                 } |                         span.innerText = comm.Message; | ||||||
|  |                     } | ||||||
|  |                 })); | ||||||
|                 if (comm.IsReply) { |                 if (comm.IsReply) { | ||||||
|                     div.classList.add('item-other'); |                     div.classList.add('item-other'); | ||||||
|                 } else { |                 } else { | ||||||
|  |                     div.classList.add('item-self'); | ||||||
|                     const [status, statusmsg] = this.#getMessageStatus(comm); |                     const [status, statusmsg] = this.#getMessageStatus(comm); | ||||||
|                     if (status !== -100) { |                     if (status !== -100) { | ||||||
|                         let statustext; |                         let statustext; | ||||||
| @@ -284,7 +407,7 @@ class CustomerCommunication { | |||||||
|                                 content.style.backgroundColor = '#ffc107'; |                                 content.style.backgroundColor = '#ffc107'; | ||||||
|                                 break; |                                 break; | ||||||
|                         } |                         } | ||||||
|                         const divstatus = createElementInit('div', div => { |                         const divstatus = createElement('div', div => { | ||||||
|                             div.className = 'item-status'; |                             div.className = 'item-status'; | ||||||
|                             div.innerText = statustext; |                             div.innerText = statustext; | ||||||
|                             if (status == -10) { |                             if (status == -10) { | ||||||
| @@ -296,7 +419,7 @@ class CustomerCommunication { | |||||||
|                 } |                 } | ||||||
|                 div.append( |                 div.append( | ||||||
|                     content, |                     content, | ||||||
|                     createElementInit('div', div => { |                     createElement('div', div => { | ||||||
|                         div.className = 'item-time'; |                         div.className = 'item-time'; | ||||||
|                         div.innerText = comm.TimeStr; |                         div.innerText = comm.TimeStr; | ||||||
|                     }) |                     }) | ||||||
| @@ -304,10 +427,10 @@ class CustomerCommunication { | |||||||
|                 children.push(div); |                 children.push(div); | ||||||
|             } |             } | ||||||
|             children[0].style.marginTop = '0'; |             children[0].style.marginTop = '0'; | ||||||
|             this.#message.append(...children); |  | ||||||
|             this.#message.scrollTop = this.#message.scrollHeight |  | ||||||
|             // setTimeout(() => this.#message.scrollTop = this.#message.scrollHeight, 0); |  | ||||||
|         } |         } | ||||||
|  |         this.#message.replaceChildren(...children); | ||||||
|  |         this.#message.scrollTop = this.#message.scrollHeight | ||||||
|  |         // setTimeout(() => this.#message.scrollTop = this.#message.scrollHeight, 0); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #getMessageStatus(comm) { |     #getMessageStatus(comm) { | ||||||
|   | |||||||
							
								
								
									
										124
									
								
								lib/app/communications/internal.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								lib/app/communications/internal.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | |||||||
|  | import "./style.scss"; | ||||||
|  | import { createElement } from "../../functions"; | ||||||
|  | import { r } from "../../utility/lgres"; | ||||||
|  | import { nullOrEmpty } from "../../utility/strings"; | ||||||
|  | import { escapeHtml } from "../../utility"; | ||||||
|  | import { setTooltip } from "../../ui/tooltip"; | ||||||
|  | import { createIcon } from "../../ui/icon"; | ||||||
|  | import { createBox } from "./lib"; | ||||||
|  |  | ||||||
|  | class InternalComment { | ||||||
|  |     #container; | ||||||
|  |     #option; | ||||||
|  |     #enter; | ||||||
|  |     #message; | ||||||
|  |  | ||||||
|  |     constructor(opt) { | ||||||
|  |         this.#option = opt ?? {}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     get text() { return this.#enter?.value } | ||||||
|  |     set text(s) { | ||||||
|  |         const element = this.#enter; | ||||||
|  |         if (element != null) { | ||||||
|  |             element.value = s | ||||||
|  |             s = String(nullOrEmpty(s) ? 0 : val.length) + '/3000'; | ||||||
|  |             this.#container.querySelector('.message-bar .prompt-count').innerText = s; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     create() { | ||||||
|  |         const container = createBox( | ||||||
|  |             createElement('div', null, | ||||||
|  |                 createElement('div', div => div.innerText = r('internalComments', 'Internal Comments')) | ||||||
|  |             ), [] | ||||||
|  |         ); | ||||||
|  |         // enter box | ||||||
|  |         const enter = createElement('textarea'); | ||||||
|  |         enter.placeholder = r('typeComment', 'Enter Comment Here'); | ||||||
|  |         enter.maxLength = 3000; | ||||||
|  |         enter.addEventListener('input', () => { | ||||||
|  |             const val = this.#enter.value; | ||||||
|  |             const s = String(nullOrEmpty(val) ? 0 : val.length) + '/3000'; | ||||||
|  |             this.#container.querySelector('.message-bar .prompt-count').innerText = s; | ||||||
|  |         }); | ||||||
|  |         this.#enter = enter; | ||||||
|  |         container.appendChild( | ||||||
|  |             createElement('div', 'message-bar', | ||||||
|  |                 enter, | ||||||
|  |                 createElement('div', div => div.style.textAlign = 'right', | ||||||
|  |                     createElement('div', 'prompt-count'), | ||||||
|  |                     createElement('button', button => { | ||||||
|  |                         button.className = 'roundbtn button-send-message'; | ||||||
|  |                         button.style.backgroundColor = 'rgb(19, 150, 204)'; | ||||||
|  |                         button.appendChild(createIcon('fa-solid', 'paper-plane')); | ||||||
|  |                         setTooltip(button, r('sendMessage', 'Send Message')); | ||||||
|  |                         button.addEventListener('click', () => { | ||||||
|  |                             if (typeof this.#option.onAddMessage === 'function') { | ||||||
|  |                                 this.#option.onAddMessage(this.#enter.value); | ||||||
|  |                             } | ||||||
|  |                         }) | ||||||
|  |                     }), | ||||||
|  |                     createElement('button', button => { | ||||||
|  |                         button.className = 'roundbtn button-post-note'; | ||||||
|  |                         button.style.border = '1px solid rgb(19, 150, 204)'; | ||||||
|  |                         button.style.fill = 'rgb(19, 150, 204)'; | ||||||
|  |                         button.appendChild(createIcon('fa-solid', 'comment-alt-lines')); | ||||||
|  |                         setTooltip(button, r('postNote', 'Post Note')); | ||||||
|  |                         button.addEventListener('click', () => { | ||||||
|  |                             if (typeof this.#option.onAddComment === 'function') { | ||||||
|  |                                 this.#option.onAddComment(this.#enter.value); | ||||||
|  |                             } | ||||||
|  |                         }) | ||||||
|  |                     }) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         const message = createElement('div', 'list-bar'); | ||||||
|  |         this.#message = message; | ||||||
|  |         container.appendChild(message); | ||||||
|  |         return this.#container = container; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     load(data) { | ||||||
|  |         const children = []; | ||||||
|  |         if (data?.length > 0) { | ||||||
|  |             for (let comment of data) { | ||||||
|  |                 const div = createElement('div', 'item-div'); | ||||||
|  |                 // if (sendto !== '') { | ||||||
|  |                 //     sendto = r('sendToColon', 'Send To :') + `\n${sendto}`; | ||||||
|  |                 // } | ||||||
|  |                 div.appendChild(createElement('div', div => { | ||||||
|  |                     div.className = 'item-poster'; | ||||||
|  |                     div.innerText = comment.UserName; | ||||||
|  |                 })); | ||||||
|  |                 const content = createElement('div', 'item-content'); | ||||||
|  |                 content.appendChild(createElement('span', span => span.innerText = escapeHtml(comment.Comment))); | ||||||
|  |                 if (comment.FollowUp?.length > 0) { | ||||||
|  |                     div.classList.add('item-sent'); | ||||||
|  |                     const sendto = r('sendToColon', 'Send To :') + '\r\n' + comment.FollowUp.split(';').join('\r\n'); | ||||||
|  |                     content.appendChild(createElement('div', div => { | ||||||
|  |                         div.className = 'item-status'; | ||||||
|  |                         div.innerText = r('sent', 'Sent'); | ||||||
|  |                         setTooltip(div, sendto); | ||||||
|  |                     })); | ||||||
|  |                 } | ||||||
|  |                 div.append( | ||||||
|  |                     content, | ||||||
|  |                     createElement('div', div => { | ||||||
|  |                         div.className = 'item-time'; | ||||||
|  |                         div.innerText = comment.SubmitDateStr; | ||||||
|  |                     }) | ||||||
|  |                 ); | ||||||
|  |                 children.push(div); | ||||||
|  |             } | ||||||
|  |             children[0].style.marginTop = '0'; | ||||||
|  |         } | ||||||
|  |         this.#message.replaceChildren(...children); | ||||||
|  |         this.#message.scrollTop = this.#message.scrollHeight | ||||||
|  |         // setTimeout(() => this.#message.scrollTop = this.#message.scrollHeight, 0); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default InternalComment; | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { createElement, createElementInit } from "../../functions"; | import { createElement } from "../../functions"; | ||||||
|  |  | ||||||
| function createBox(title, functions) { | function createBox(title, functions) { | ||||||
|     const container = createElement('div', 'comm'); |     const container = createElement('div', 'comm'); | ||||||
|   | |||||||
| @@ -1,10 +1,6 @@ | |||||||
| :root { | :root { | ||||||
|     --dark-fore-color: #fff; |  | ||||||
|     --dark-fore-opacity-color: rgba(255, 255, 255, .6); |  | ||||||
|     --title-color: #fff; |     --title-color: #fff; | ||||||
|     --title-bg-color: rgb(68, 114, 196); |     --title-bg-color: rgb(68, 114, 196); | ||||||
|     --strong-color: #333; |  | ||||||
|     --medium-font-size: .875rem; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| .comm { | .comm { | ||||||
| @@ -13,6 +9,15 @@ | |||||||
|     width: 320px; |     width: 320px; | ||||||
|     background-color: var(--dark-fore-color); |     background-color: var(--dark-fore-color); | ||||||
|     border: 1px solid var(--title-bg-color); |     border: 1px solid var(--title-bg-color); | ||||||
|  |     margin-left: 12px; | ||||||
|  |  | ||||||
|  |     & { | ||||||
|  |         --dark-fore-color: #fff; | ||||||
|  |         --dark-fore-opacity-color: rgba(255, 255, 255, .6); | ||||||
|  |         --strong-color: #333; | ||||||
|  |         --light-color: #ccc; | ||||||
|  |         --medium-font-size: .875rem; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     .roundbtn { |     .roundbtn { | ||||||
|         width: 30px; |         width: 30px; | ||||||
| @@ -86,6 +91,7 @@ | |||||||
|         line-height: 24px; |         line-height: 24px; | ||||||
|         display: flex; |         display: flex; | ||||||
|         align-items: center; |         align-items: center; | ||||||
|  |         font-size: 1.2em; | ||||||
|  |  | ||||||
|         >div { |         >div { | ||||||
|             flex: 1 1 auto; |             flex: 1 1 auto; | ||||||
| @@ -94,6 +100,7 @@ | |||||||
|         >.title-functions { |         >.title-functions { | ||||||
|             flex: 0 0 auto; |             flex: 0 0 auto; | ||||||
|             display: flex; |             display: flex; | ||||||
|  |             padding: 4px; | ||||||
|  |  | ||||||
|             >label { |             >label { | ||||||
|                 margin: 0 4px; |                 margin: 0 4px; | ||||||
| @@ -122,6 +129,19 @@ | |||||||
|                         opacity: .6; |                         opacity: .6; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |                 &.disabled { | ||||||
|  |                     cursor: default; | ||||||
|  |                     opacity: .6; | ||||||
|  |  | ||||||
|  |                     &:hover { | ||||||
|  |                         background-color: var(--dark-fore-color); | ||||||
|  |  | ||||||
|  |                         >svg { | ||||||
|  |                             opacity: unset; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -132,29 +152,41 @@ | |||||||
|         display: flex; |         display: flex; | ||||||
|         border-bottom: 1px solid var(--title-bg-color); |         border-bottom: 1px solid var(--title-bg-color); | ||||||
|  |  | ||||||
|         >svg { |         >.bar-icon { | ||||||
|             width: 30px; |             flex: 0 0 auto; | ||||||
|             height: 30px; |  | ||||||
|             margin: 0 8px; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         .contact-item { |  | ||||||
|             display: flex; |  | ||||||
|             align-items: center; |  | ||||||
|             line-height: 22px; |  | ||||||
|  |  | ||||||
|             >svg { |             >svg { | ||||||
|                 flex: 0 0 auto; |                 width: 30px; | ||||||
|                 width: 16px; |                 height: 30px; | ||||||
|                 height: 16px; |                 margin: 0 8px; | ||||||
|                 margin-right: 6px; |  | ||||||
|                 fill: var(--strong-color); |  | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|             >span { |         >.bar-list { | ||||||
|                 flex: 1 1 auto; |             flex: 1 1 auto; | ||||||
|                 color: var(--strong-color); |             width: calc(100% - 46px); | ||||||
|                 font-size: var(--medium-font-size); |  | ||||||
|  |             .contact-item { | ||||||
|  |                 display: flex; | ||||||
|  |                 align-items: center; | ||||||
|  |                 line-height: 22px; | ||||||
|  |  | ||||||
|  |                 >svg { | ||||||
|  |                     flex: 0 0 auto; | ||||||
|  |                     width: 16px; | ||||||
|  |                     height: 16px; | ||||||
|  |                     margin-right: 6px; | ||||||
|  |                     fill: var(--strong-color); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 >span { | ||||||
|  |                     flex: 1 1 auto; | ||||||
|  |                     color: var(--strong-color); | ||||||
|  |                     font-size: var(--medium-font-size); | ||||||
|  |                     overflow: hidden; | ||||||
|  |                     text-overflow: ellipsis; | ||||||
|  |                     padding-right: 10px; | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -182,6 +214,11 @@ | |||||||
|  |  | ||||||
|         >div { |         >div { | ||||||
|             padding: 0 10px 10px; |             padding: 0 10px 10px; | ||||||
|  |  | ||||||
|  |             >.prompt-count { | ||||||
|  |                 display: inline-block; | ||||||
|  |                 color: var(--light-color); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -206,64 +243,78 @@ | |||||||
|             &:last-child { |             &:last-child { | ||||||
|                 border-bottom: none; |                 border-bottom: none; | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|  |  | ||||||
|         .item-poster { |             .item-poster { | ||||||
|             font-weight: bold; |                 font-weight: bold; | ||||||
|             align-self: flex-end; |                 align-self: flex-start; | ||||||
|  |  | ||||||
|             .tooltip-wrapper>.tooltip-content { |                 .tooltip-wrapper>.tooltip-content { | ||||||
|                 font-weight: normal; |                     font-weight: normal; | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         .item-content { |  | ||||||
|             line-height: 1.2rem; |  | ||||||
|             padding: 8px 20px; |  | ||||||
|             border-radius: 5px; |  | ||||||
|             white-space: pre-wrap; |  | ||||||
|             word-break: break-word; |  | ||||||
|             max-width: 240px; |  | ||||||
|             margin-right: 10px; |  | ||||||
|             background-color: #9eea6a; |  | ||||||
|             align-self: flex-end; |  | ||||||
|  |  | ||||||
|             a>svg { |  | ||||||
|                 width: 13px; |  | ||||||
|                 height: 13px; |  | ||||||
|                 fill: #2140fb; |  | ||||||
|  |  | ||||||
|                 &:hover { |  | ||||||
|                     border-bottom: 1px solid; |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             .item-status { |  | ||||||
|                 text-align: right; |  | ||||||
|                 margin-top: 3px; |  | ||||||
|                 font-weight: bold; |  | ||||||
|                 margin-right: -12px; |  | ||||||
|                 font-size: 10px; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         .item-time { |  | ||||||
|             align-self: flex-end; |  | ||||||
|             color: #aaa; |  | ||||||
|             font-size: .7rem; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         &.item-other { |  | ||||||
|  |  | ||||||
|             .item-poster, |  | ||||||
|             .item-content { |  | ||||||
|                 align-self: flex-start; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             .item-content { |             .item-content { | ||||||
|  |                 line-height: 1.2rem; | ||||||
|  |                 padding: 8px 20px; | ||||||
|  |                 border-radius: 5px; | ||||||
|  |                 white-space: pre-wrap; | ||||||
|  |                 word-break: break-word; | ||||||
|  |                 max-width: 240px; | ||||||
|                 background-color: rgb(244, 244, 244); |                 background-color: rgb(244, 244, 244); | ||||||
|                 margin-right: unset; |  | ||||||
|                 margin-left: 10px; |                 a>svg { | ||||||
|  |                     width: 13px; | ||||||
|  |                     height: 13px; | ||||||
|  |                     fill: #2140fb; | ||||||
|  |  | ||||||
|  |                     &:hover { | ||||||
|  |                         border-bottom: 1px solid; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 >span::after { | ||||||
|  |                     content: ''; | ||||||
|  |                     display: block; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 .item-status { | ||||||
|  |                     text-align: right; | ||||||
|  |                     margin-top: 3px; | ||||||
|  |                     font-weight: bold; | ||||||
|  |                     margin-right: -12px; | ||||||
|  |                     font-size: 10px; | ||||||
|  |                     float: right; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             .item-time { | ||||||
|  |                 align-self: flex-end; | ||||||
|  |                 color: #aaa; | ||||||
|  |                 font-size: .7rem; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             &.item-other { | ||||||
|  |                 .item-content { | ||||||
|  |                     margin-left: 10px; | ||||||
|  |                     align-self: flex-start; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             &.item-self { | ||||||
|  |  | ||||||
|  |                 .item-poster, | ||||||
|  |                 .item-content { | ||||||
|  |                     align-self: flex-end; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 .item-content { | ||||||
|  |                     margin-right: 10px; | ||||||
|  |                     background-color: #9eea6a; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             &.item-sent .item-content { | ||||||
|  |                 background-color: rgb(164, 226, 251); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								lib/functions.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								lib/functions.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1 @@ | |||||||
| export function createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, className?: string, ...children?: (Node | string)[]): HTMLElementTagNameMap[K]; | export function createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, init?: string | ((element: HTMLElementTagNameMap[K]) => void), ...children?: (Node | string)[]): HTMLElementTagNameMap[K]; | ||||||
| export function createElementInit<K extends keyof HTMLElementTagNameMap>(tagName: K, init?: (element: HTMLElementTagNameMap[K]) => void, ...children?: (Node | string)[]): HTMLElementTagNameMap[K]; |  | ||||||
| @@ -1,19 +1,12 @@ | |||||||
| function createElement(tagName, className, ...children) { | export function createElement(tagName, init, ...children) { | ||||||
|     return createElementInit(tagName, className && (element => element.className = className), ...children); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function createElementInit(tagName, init, ...children) { |  | ||||||
|     const element = document.createElement(tagName); |     const element = document.createElement(tagName); | ||||||
|     if (typeof init === 'function') { |     if (typeof init === 'function') { | ||||||
|         init(element); |         init(element); | ||||||
|  |     } else if (init != null) { | ||||||
|  |         element.className = init; | ||||||
|     } |     } | ||||||
|     if (children.length > 0) { |     if (children.length > 0) { | ||||||
|         element.append(...children); |         element.append(...children); | ||||||
|     } |     } | ||||||
|     return element; |     return element; | ||||||
| } | } | ||||||
|  |  | ||||||
| export { |  | ||||||
|     createElement, |  | ||||||
|     createElementInit |  | ||||||
| } |  | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { createElement, createElementInit } from "../functions"; | import { createElement } from "../functions"; | ||||||
| import { createIcon } from "./icon"; | import { createIcon } from "./icon"; | ||||||
|  |  | ||||||
| function fillCheckbox(container, type, label) { | function fillCheckbox(container, type, label) { | ||||||
| @@ -9,14 +9,14 @@ function fillCheckbox(container, type, label) { | |||||||
|         container.appendChild(label); |         container.appendChild(label); | ||||||
|     } else if (label?.length > 0) { |     } else if (label?.length > 0) { | ||||||
|         container.appendChild( |         container.appendChild( | ||||||
|             createElementInit('span', span => span.innerText = label) |             createElement('span', span => span.innerText = label) | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function createCheckbox(opts = {}) { | function createCheckbox(opts = {}) { | ||||||
|     const container = createElement('label', 'checkbox-wrapper', |     const container = createElement('label', 'checkbox-wrapper', | ||||||
|         createElementInit('input', input => { |         createElement('input', input => { | ||||||
|             input.setAttribute('type', 'checkbox'); |             input.setAttribute('type', 'checkbox'); | ||||||
|             if (opts.checked === true) { |             if (opts.checked === true) { | ||||||
|                 input.checked = true; |                 input.checked = true; | ||||||
|   | |||||||
| @@ -279,7 +279,7 @@ | |||||||
|       display: flex; |       display: flex; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #dropdown-sample>.dropdown-wrapper { |     #dropdown-sample>.drop-wrapper { | ||||||
|       width: 200px; |       width: 200px; | ||||||
|       margin-right: 10px; |       margin-right: 10px; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ if (dropdownGlobal == null) { | |||||||
|         configurable: false, |         configurable: false, | ||||||
|         enumerable: false, |         enumerable: false, | ||||||
|         value: function () { |         value: function () { | ||||||
|             const panel = document.querySelector('.dropdown-wrapper .dropdown-box.active'); |             const panel = document.querySelector('.drop-wrapper .drop-box.active'); | ||||||
|             if (panel == null) { |             if (panel == null) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| @@ -40,7 +40,7 @@ if (dropdownGlobal == null) { | |||||||
|     document.addEventListener('mousedown', e => { |     document.addEventListener('mousedown', e => { | ||||||
|         let parent = e.target; |         let parent = e.target; | ||||||
|         while (parent != null) { |         while (parent != null) { | ||||||
|             if (parent.classList.contains('dropdown-box')) { |             if (parent.classList.contains('drop-box')) { | ||||||
|                 e.stopPropagation(); |                 e.stopPropagation(); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| @@ -111,14 +111,14 @@ class Dropdown { | |||||||
|         const options = this.#options; |         const options = this.#options; | ||||||
|  |  | ||||||
|         // wrapper |         // wrapper | ||||||
|         const wrapper = createElement('div', 'dropdown-wrapper'); |         const wrapper = createElement('div', 'drop-wrapper'); | ||||||
|         const dropId = String(Math.random()).substring(2); |         const dropId = String(Math.random()).substring(2); | ||||||
|         wrapper.dataset.dropId = dropId; |         wrapper.dataset.dropId = dropId; | ||||||
|         dropdownGlobal[dropId] = this; |         dropdownGlobal[dropId] = this; | ||||||
|         this.#wrapper = wrapper; |         this.#wrapper = wrapper; | ||||||
|  |  | ||||||
|         // header |         // header | ||||||
|         const header = createElement('div', 'dropdown-header'); |         const header = createElement('div', 'drop-header'); | ||||||
|         header.addEventListener('click', () => { |         header.addEventListener('click', () => { | ||||||
|             if (this.disabled) { |             if (this.disabled) { | ||||||
|                 return; |                 return; | ||||||
| @@ -136,7 +136,7 @@ class Dropdown { | |||||||
|         // label or input |         // label or input | ||||||
|         let label; |         let label; | ||||||
|         if (options.input) { |         if (options.input) { | ||||||
|             label = createElement('input', 'dropdown-text'); |             label = createElement('input', 'drop-text'); | ||||||
|             label.setAttribute('type', 'text'); |             label.setAttribute('type', 'text'); | ||||||
|             options.placeholder && label.setAttribute('placeholder', options.placeholder); |             options.placeholder && label.setAttribute('placeholder', options.placeholder); | ||||||
|             isPositive(options.maxlength) && label.setAttribute('maxlength', options.maxlength); |             isPositive(options.maxlength) && label.setAttribute('maxlength', options.maxlength); | ||||||
| @@ -151,7 +151,7 @@ class Dropdown { | |||||||
|             label.addEventListener('mousedown', e => this.#expanded && e.stopPropagation()); |             label.addEventListener('mousedown', e => this.#expanded && e.stopPropagation()); | ||||||
|         } else { |         } else { | ||||||
|             isPositive(options.tabindex) && header.setAttribute('tabindex', options.tabindex); |             isPositive(options.tabindex) && header.setAttribute('tabindex', options.tabindex); | ||||||
|             label = createElement('label', 'dropdown-text'); |             label = createElement('label', 'drop-text'); | ||||||
|         } |         } | ||||||
|         this.#label = label; |         this.#label = label; | ||||||
|         if (options.multiselect) { |         if (options.multiselect) { | ||||||
| @@ -164,7 +164,7 @@ class Dropdown { | |||||||
|         } else if (options.selected != null) { |         } else if (options.selected != null) { | ||||||
|             this.select(options.selected, true); |             this.select(options.selected, true); | ||||||
|         } |         } | ||||||
|         header.append(label, createElement('label', 'dropdown-caret')); |         header.append(label, createElement('label', 'drop-caret')); | ||||||
|         wrapper.appendChild(header); |         wrapper.appendChild(header); | ||||||
|  |  | ||||||
|         this.disabled = options.disabled || false; |         this.disabled = options.disabled || false; | ||||||
| @@ -173,16 +173,16 @@ class Dropdown { | |||||||
|  |  | ||||||
|     get multiselect() { return this.#options.multiselect } |     get multiselect() { return this.#options.multiselect } | ||||||
|  |  | ||||||
|     get disabled() { return this.#wrapper == null || this.#wrapper.querySelector('.dropdown-header.disabled') != null } |     get disabled() { return this.#wrapper == null || this.#wrapper.querySelector('.drop-header.disabled') != null } | ||||||
|  |  | ||||||
|     set disabled(flag) { |     set disabled(flag) { | ||||||
|         if (this.#wrapper == null) { |         if (this.#wrapper == null) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         if (flag) { |         if (flag) { | ||||||
|             this.#wrapper.querySelector('.dropdown-header').classList.add('disabled'); |             this.#wrapper.querySelector('.drop-header').classList.add('disabled'); | ||||||
|         } else { |         } else { | ||||||
|             this.#wrapper.querySelector('.dropdown-header').classList.remove('disabled'); |             this.#wrapper.querySelector('.drop-header').classList.remove('disabled'); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -275,16 +275,16 @@ class Dropdown { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     get #expanded() { return this.#container?.style.visibility === 'visible' } |     get #expanded() { return this.#container?.style?.visibility === 'visible' } | ||||||
|  |  | ||||||
|     #dropdown(flag = true) { |     #dropdown(flag = true) { | ||||||
|         const options = this.#options; |         const options = this.#options; | ||||||
|         let panel = this.#container; |         let panel = this.#container; | ||||||
|         if (panel == null) { |         if (panel == null) { | ||||||
|             panel = createElement('div', 'dropdown-box'); |             panel = createElement('div', 'drop-box'); | ||||||
|             // search box |             // search box | ||||||
|             if (!options.input && options.search) { |             if (!options.input && options.search) { | ||||||
|                 const search = createElement('div', 'dropdown-search'); |                 const search = createElement('div', 'drop-search'); | ||||||
|                 const input = createElement('input'); |                 const input = createElement('input'); | ||||||
|                 input.setAttribute('type', 'text'); |                 input.setAttribute('type', 'text'); | ||||||
|                 isPositive(options.tabindex) && input.setAttribute('tabindex', options.tabindex); |                 isPositive(options.tabindex) && input.setAttribute('tabindex', options.tabindex); | ||||||
| @@ -298,7 +298,7 @@ class Dropdown { | |||||||
|                 panel.appendChild(search); |                 panel.appendChild(search); | ||||||
|             } |             } | ||||||
|             // list |             // list | ||||||
|             const list = createElement('ul', 'dropdown-list'); |             const list = createElement('ul', 'drop-list'); | ||||||
|             if (!this.multiselect) { |             if (!this.multiselect) { | ||||||
|                 list.addEventListener('click', e => { |                 list.addEventListener('click', e => { | ||||||
|                     let li = e.target; |                     let li = e.target; | ||||||
| @@ -321,7 +321,7 @@ class Dropdown { | |||||||
|         if (flag) { |         if (flag) { | ||||||
|             let source = this.source; |             let source = this.source; | ||||||
|             if (!options.input && options.search) { |             if (!options.input && options.search) { | ||||||
|                 const search = panel.querySelector('.dropdown-search > input'); |                 const search = panel.querySelector('.drop-search > input'); | ||||||
|                 if (!nullOrEmpty(search?.value)) { |                 if (!nullOrEmpty(search?.value)) { | ||||||
|                     source = filterSource(options.searchkeys, options.textkey, search.value, source); |                     source = filterSource(options.searchkeys, options.textkey, search.value, source); | ||||||
|                 } |                 } | ||||||
| @@ -348,7 +348,7 @@ class Dropdown { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     #filllist(source) { |     #filllist(source) { | ||||||
|         const list = this.#container.querySelector('.dropdown-list'); |         const list = this.#container.querySelector('.drop-list'); | ||||||
|         list.replaceChildren(); |         list.replaceChildren(); | ||||||
|         const multiselect = this.multiselect; |         const multiselect = this.multiselect; | ||||||
|         const allchecked = this.#allChecked; |         const allchecked = this.#allChecked; | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								lib/ui/grid.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								lib/ui/grid.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -5,6 +5,11 @@ interface GridItem { | |||||||
|     displayValue: string; |     displayValue: string; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | interface GridSourceItem { | ||||||
|  |     value: string; | ||||||
|  |     text: string; | ||||||
|  | } | ||||||
|  |  | ||||||
| declare var GridColumn: { | declare var GridColumn: { | ||||||
|     create(): HTMLElement; |     create(): HTMLElement; | ||||||
|     createEdit(trigger: (e: any) => void, col: GridColumnDefinition, body: HTMLElement): HTMLElement; |     createEdit(trigger: (e: any) => void, col: GridColumnDefinition, body: HTMLElement): HTMLElement; | ||||||
| @@ -44,7 +49,7 @@ interface GridColumnDefinition { | |||||||
|     sortFilter?: (a: GridItem | any, b: GridItem | any) => -1 | 0 | 1; |     sortFilter?: (a: GridItem | any, b: GridItem | any) => -1 | 0 | 1; | ||||||
|     bgFilter?: (item: GridItem | any) => string; |     bgFilter?: (item: GridItem | any) => string; | ||||||
|     dropOptions?: DropdownOptions; |     dropOptions?: DropdownOptions; | ||||||
|     source?: Array<any> | ((item: GridItem | any) => Array<any>); |     source?: Array<any> | ((item: GridItem | any) => Array<any> | Promise<Array<GridSourceItem>>); | ||||||
|     iconType?: string; |     iconType?: string; | ||||||
|     text?: string; |     text?: string; | ||||||
|     tooltip?: string; |     tooltip?: string; | ||||||
|   | |||||||
| @@ -85,7 +85,7 @@ | |||||||
|       sortFilter?: (a: GridItem | any, b: GridItem | any) => -1 | 0 | 1; |       sortFilter?: (a: GridItem | any, b: GridItem | any) => -1 | 0 | 1; | ||||||
|       bgFilter?: (item: GridItem | any) => string; |       bgFilter?: (item: GridItem | any) => string; | ||||||
|       dropOptions?: DropdownOptions; |       dropOptions?: DropdownOptions; | ||||||
|       source?: Array<any> | ((item: GridItem | any) => Array<any>); |       source?: Array<any> | ((item: GridItem | any) => Array<any> | Promise<Array<GridSourceItem>>); | ||||||
|       iconType?: string; |       iconType?: string; | ||||||
|       text?: string; |       text?: string; | ||||||
|       tooltip?: string; |       tooltip?: string; | ||||||
| @@ -136,8 +136,8 @@ | |||||||
|   <samp>val: any</samp> |   <samp>val: any</samp> | ||||||
|   <p>单元格的值</p> |   <p>单元格的值</p> | ||||||
|   <samp>item: GridItem | any</samp> |   <samp>item: GridItem | any</samp> | ||||||
|   <p>单元格所在行的数据项/p> |   <p>单元格所在行的数据项</p> | ||||||
|     <samp>col: GridColumnDefinition</samp> |   <samp>col: GridColumnDefinition</samp> | ||||||
|   <p>单元格所在列的定义对象</p> |   <p>单元格所在列的定义对象</p> | ||||||
|   <hr /> |   <hr /> | ||||||
|   <h2>示例</h2> |   <h2>示例</h2> | ||||||
| @@ -198,12 +198,22 @@ | |||||||
|           key: 'c2a', |           key: 'c2a', | ||||||
|           caption: '下拉', |           caption: '下拉', | ||||||
|           type: Grid.ColumnTypes.Dropdown, |           type: Grid.ColumnTypes.Dropdown, | ||||||
|           source: [ |           source: item => { | ||||||
|             { value: 'off', text: 'Off' }, |             if (item.source == null) { | ||||||
|             { value: 'pending', text: 'Pending' }, |               return new Promise((resolve, reject) => { | ||||||
|             { value: 'broken', text: 'Broken' }, |                 setTimeout(() => { | ||||||
|             { value: 'running', text: 'Running' } |                   item.source = [ | ||||||
|           ], |                     { value: 'off', text: 'Off' }, | ||||||
|  |                     { value: 'pending', text: 'Pending' }, | ||||||
|  |                     { value: 'broken', text: 'Broken' }, | ||||||
|  |                     { value: 'running', text: 'Running' } | ||||||
|  |                   ]; | ||||||
|  |                   resolve(item.source); | ||||||
|  |                 }, 2000); | ||||||
|  |               }); | ||||||
|  |             } | ||||||
|  |             return item.source; | ||||||
|  |           }, | ||||||
|           enabled: 'enabled', |           enabled: 'enabled', | ||||||
|           onchanged: (item, value) => console.log('dropdown changed', item, value) |           onchanged: (item, value) => console.log('dropdown changed', item, value) | ||||||
|         }, |         }, | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { global, isPositive, isMobile, throttle, truncate } from "../utility"; | import { global, isPositive, isMobile, throttle, truncate } from "../utility"; | ||||||
| import { nullOrEmpty } from "../utility/strings"; | import { nullOrEmpty } from "../utility/strings"; | ||||||
| import { r } from "../utility/lgres"; | import { r } from "../utility/lgres"; | ||||||
| import { createElement, createElementInit } from "../functions"; | import { createElement } from "../functions"; | ||||||
| import { createIcon } from "./icon"; | import { createIcon } from "./icon"; | ||||||
| import { createCheckbox } from "./checkbox"; | import { createCheckbox } from "./checkbox"; | ||||||
| import { setTooltip } from "./tooltip"; | import { setTooltip } from "./tooltip"; | ||||||
| @@ -85,7 +85,7 @@ class GridDropdownColumn extends GridColumn { | |||||||
|         return drop.create(); |         return drop.create(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     static getDrop(element) { |     static #getDrop(element) { | ||||||
|         const dropGlobal = global[SymbolDropdown]; |         const dropGlobal = global[SymbolDropdown]; | ||||||
|         if (dropGlobal == null) { |         if (dropGlobal == null) { | ||||||
|             return null; |             return null; | ||||||
| @@ -98,7 +98,7 @@ class GridDropdownColumn extends GridColumn { | |||||||
|         return drop; |         return drop; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     static getSource(item, col) { |     static #getSource(item, col) { | ||||||
|         let source = col.source; |         let source = col.source; | ||||||
|         if (typeof source === 'function') { |         if (typeof source === 'function') { | ||||||
|             source = source(item); |             source = source(item); | ||||||
| @@ -106,23 +106,37 @@ class GridDropdownColumn extends GridColumn { | |||||||
|         return source; |         return source; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     static #setValue(source, element, val) { | ||||||
|  |         const data = source?.find(v => v.value === val); | ||||||
|  |         if (data != null) { | ||||||
|  |             val = data.text; | ||||||
|  |         } | ||||||
|  |         super.setValue(element, val); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     static setValue(element, val, item, col) { |     static setValue(element, val, item, col) { | ||||||
|         if (element.tagName !== 'DIV') { |         if (element.tagName !== 'DIV') { | ||||||
|             let source = this.getSource(item, col); |             let source = this.#getSource(item, col); | ||||||
|             const data = source?.find(v => v.value === val); |             if (source instanceof Promise) { | ||||||
|             if (data != null) { |                 source.then(s => this.#setValue(s, element, val)); | ||||||
|                 val = data.text; |             } else { | ||||||
|  |                 this.#setValue(source, element, val); | ||||||
|             } |             } | ||||||
|             super.setValue(element, val); |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         const drop = this.getDrop(element); |         const drop = this.#getDrop(element); | ||||||
|         if (drop == null) { |         if (drop == null) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         if (drop.source == null || drop.source.length === 0) { |         if (drop.source == null || drop.source.length === 0) { | ||||||
|             let source = this.getSource(item, col); |             let source = this.#getSource(item, col); | ||||||
|             if (source != null) { |             if (source instanceof Promise) { | ||||||
|  |                 source.then(s => { | ||||||
|  |                     drop.source = s; | ||||||
|  |                     drop.select(val, true); | ||||||
|  |                 }) | ||||||
|  |                 return; | ||||||
|  |             } else if (source != null) { | ||||||
|                 drop.source = source; |                 drop.source = source; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -134,7 +148,7 @@ class GridDropdownColumn extends GridColumn { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     static setEnabled(element, enabled) { |     static setEnabled(element, enabled) { | ||||||
|         const drop = this.getDrop(element); |         const drop = this.#getDrop(element); | ||||||
|         if (drop == null) { |         if (drop == null) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @@ -316,7 +330,7 @@ class Grid { | |||||||
|  |  | ||||||
|     get selectedIndex() { return (this.#selectedIndexes && this.#selectedIndexes[0]) ?? -1 } |     get selectedIndex() { return (this.#selectedIndexes && this.#selectedIndexes[0]) ?? -1 } | ||||||
|  |  | ||||||
|     get loading() { return this.#refs.loading?.style.visibility === 'visible' } |     get loading() { return this.#refs.loading?.style?.visibility === 'visible' } | ||||||
|     set loading(flag) { |     set loading(flag) { | ||||||
|         if (this.#refs.loading == null) { |         if (this.#refs.loading == null) { | ||||||
|             return; |             return; | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								lib/ui/popup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								lib/ui/popup.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | import "../../css/popup.scss"; | ||||||
|  | import { createElement } from "../functions"; | ||||||
|  | import { createIcon } from "./icon"; | ||||||
|  |  | ||||||
|  | function createPopup(title, content, ...buttons) { | ||||||
|  |     const mask = createElement('div', 'popup-mask'); | ||||||
|  |     const container = createElement('div', 'popup-container'); | ||||||
|  |     const close = () => { | ||||||
|  |         mask.classList.add('popup-active'); | ||||||
|  |         mask.style.opacity = 0; | ||||||
|  |         setTimeout(() => mask.remove(), 120); | ||||||
|  |     }; | ||||||
|  |     container.append( | ||||||
|  |         createElement('div', header => { | ||||||
|  |             header.className = 'popup-header'; | ||||||
|  |             if (title instanceof HTMLElement) { | ||||||
|  |                 header.appendChild(title); | ||||||
|  |             } else { | ||||||
|  |                 header.appendChild(createElement('div', t => t.innerText = title)); | ||||||
|  |             } | ||||||
|  |             const cancel = createIcon('fa-regular', 'times'); | ||||||
|  |             cancel.addEventListener('click', () => close()); | ||||||
|  |             header.appendChild(cancel); | ||||||
|  |         }), | ||||||
|  |         createElement('div', 'popup-body', content), | ||||||
|  |         createElement('div', 'popup-footer', ...buttons.map(b => { | ||||||
|  |             const button = createElement('div', 'popup-button'); | ||||||
|  |             button.innerText = b.text; | ||||||
|  |             if (typeof b.trigger === 'function') { | ||||||
|  |                 button.addEventListener('click', () => { | ||||||
|  |                     if (b.trigger(container) === false) { | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     close(); | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         })) | ||||||
|  |     ); | ||||||
|  |     mask.appendChild(container); | ||||||
|  |     return mask; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export { | ||||||
|  |     createPopup | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								lib/ui/tooltip.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								lib/ui/tooltip.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,2 @@ | |||||||
| export function setTooltip(container: HTMLElement, content: string | HTMLElement): HTMLElement | export function setTooltip(container: HTMLElement, content: string | HTMLElement, flag?: boolean): HTMLElement | ||||||
| export function resolveTooltip(container?: HTMLElement): HTMLElement | export function resolveTooltip(container?: HTMLElement): HTMLElement | ||||||
| @@ -5,7 +5,7 @@ | |||||||
|         给某个元素或者页面上含有 title 属性的元素设置一个统一样式的 tooltip。 |         给某个元素或者页面上含有 title 属性的元素设置一个统一样式的 tooltip。 | ||||||
|     </p> |     </p> | ||||||
|     <h2>setTooltip</h2> |     <h2>setTooltip</h2> | ||||||
|     <code>function setTooltip(container: HTMLElement, content: string | HTMLElement): void</code> |     <code>function setTooltip(container: HTMLElement, content: string | HTMLElement, flag?: boolean): void</code> | ||||||
|     <h3>container: HTMLElement</h3> |     <h3>container: HTMLElement</h3> | ||||||
|     <p> |     <p> | ||||||
|         要设置 tooltip 的元素 |         要设置 tooltip 的元素 | ||||||
| @@ -14,6 +14,10 @@ | |||||||
|     <p> |     <p> | ||||||
|         要设置的 tooltip 内容,允许为字符串或者 HTML 元素 |         要设置的 tooltip 内容,允许为字符串或者 HTML 元素 | ||||||
|     </p> |     </p> | ||||||
|  |     <h3>flag?: boolean</h3> | ||||||
|  |     <p> | ||||||
|  |         是否启用严格模式,只有显示不完整时才显示 tooltip | ||||||
|  |     </p> | ||||||
|     <h2>resolveTooltip</h2> |     <h2>resolveTooltip</h2> | ||||||
|     <code>function resolveTooltip(container?: HTMLElement): HTMLElement</code> |     <code>function resolveTooltip(container?: HTMLElement): HTMLElement</code> | ||||||
|     <h3>container?: HTMLElement</h3> |     <h3>container?: HTMLElement</h3> | ||||||
|   | |||||||
| @@ -1,18 +1,18 @@ | |||||||
| import { createElement, createElementInit } from "../functions"; | import { createElement } from "../functions"; | ||||||
|  |  | ||||||
| function setTooltip(container, content) { | function setTooltip(container, content, flag = false) { | ||||||
|     const tip = container.querySelector('.tooltip-wrapper'); |     const tip = container.querySelector('.tooltip-wrapper'); | ||||||
|     if (tip != null) { |     if (tip != null) { | ||||||
|         tip.remove(); |         tip.remove(); | ||||||
|     } |     } | ||||||
|     const wrapper = createElementInit('div', wrapper => { |     const wrapper = createElement('div', wrapper => { | ||||||
|         wrapper.className = 'tooltip-wrapper tooltip-color'; |         wrapper.className = 'tooltip-wrapper tooltip-color'; | ||||||
|         wrapper.style.visibility = 'hidden'; |         wrapper.style.visibility = 'hidden'; | ||||||
|         wrapper.style.opacity = 0; |         wrapper.style.opacity = 0; | ||||||
|     }, |     }, | ||||||
|         createElement('div', 'tooltip-pointer tooltip-color'), |         createElement('div', 'tooltip-pointer tooltip-color'), | ||||||
|         createElement('div', 'tooltip-curtain tooltip-color'), |         createElement('div', 'tooltip-curtain tooltip-color'), | ||||||
|         createElementInit('div', cnt => { |         createElement('div', cnt => { | ||||||
|             cnt.className = 'tooltip-content'; |             cnt.className = 'tooltip-content'; | ||||||
|             if (content instanceof HTMLElement) { |             if (content instanceof HTMLElement) { | ||||||
|                 cnt.appendChild(content); |                 cnt.appendChild(content); | ||||||
| @@ -27,32 +27,35 @@ function setTooltip(container, content) { | |||||||
|     let tid; |     let tid; | ||||||
|     container.addEventListener('mouseenter', () => { |     container.addEventListener('mouseenter', () => { | ||||||
|         tid && clearTimeout(tid); |         tid && clearTimeout(tid); | ||||||
|         tid = setTimeout(() => { |         let c = container; | ||||||
|             while (container?.offsetWidth == null) { |         while (c?.offsetWidth == null) { | ||||||
|                 container = container.parentElement; |             c = c.parentElement; | ||||||
|             } |         } | ||||||
|             if (container == null) { |         if (c == null) { | ||||||
|                 return; |             return; | ||||||
|             } |         } | ||||||
|             let parent = container; |         if (!flag || c.scrollWidth > c.offsetWidth) { | ||||||
|             let left = container.offsetLeft; |             tid = setTimeout(() => { | ||||||
|             let top = container.offsetTop; |                 let parent = c; | ||||||
|             while ((parent = parent.offsetParent) != null) { |                 let left = c.offsetLeft; | ||||||
|                 left += parent.offsetLeft; |                 let top = c.offsetTop; | ||||||
|                 top += parent.offsetTop; |                 while ((parent = parent.offsetParent) != null) { | ||||||
|             } |                     left += parent.offsetLeft; | ||||||
|             parent = container; |                     top += parent.offsetTop; | ||||||
|             while ((parent = parent.parentElement) != null) { |                 } | ||||||
|                 left -= parent.scrollLeft; |                 parent = c; | ||||||
|                 top -= parent.scrollTop; |                 while ((parent = parent.parentElement) != null) { | ||||||
|             } |                     left -= parent.scrollLeft; | ||||||
|             left -= wrapper.offsetWidth / 2 - container.offsetWidth / 2; |                     top -= parent.scrollTop; | ||||||
|             top -= wrapper.offsetHeight + 14; |                 } | ||||||
|             wrapper.style.left = `${left}px`; |                 left -= wrapper.offsetWidth / 2 - c.offsetWidth / 2; | ||||||
|             wrapper.style.top = `${top}px`; |                 top -= wrapper.offsetHeight + 14; | ||||||
|             wrapper.style.visibility = 'visible'; |                 wrapper.style.left = `${left}px`; | ||||||
|             wrapper.style.opacity = 1; |                 wrapper.style.top = `${top}px`; | ||||||
|         }, 100); |                 wrapper.style.visibility = 'visible'; | ||||||
|  |                 wrapper.style.opacity = 1; | ||||||
|  |             }, 100); | ||||||
|  |         } | ||||||
|     }); |     }); | ||||||
|     container.addEventListener('mouseleave', () => { |     container.addEventListener('mouseleave', () => { | ||||||
|         tid && clearTimeout(tid); |         tid && clearTimeout(tid); | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { getCookie, setCookie, deleteCookie } from "./utility/cookie"; | import { getCookie, setCookie, deleteCookie } from "./utility/cookie"; | ||||||
| import { init, r, lang } from "./utility/lgres"; | import { init, r, lang } from "./utility/lgres"; | ||||||
| import { get, post, upload } from "./utility/request"; | import { get, post, upload } from "./utility/request"; | ||||||
| import { nullOrEmpty, contains, endsWith, padStart, formatUrl } from "./utility/strings"; | import { nullOrEmpty, contains, endsWith, padStart, formatUrl, escapeHtml } from "./utility/strings"; | ||||||
|  |  | ||||||
| let g = typeof globalThis !== 'undefined' ? globalThis : self; | let g = typeof globalThis !== 'undefined' ? globalThis : self; | ||||||
|  |  | ||||||
| @@ -58,6 +58,7 @@ export { | |||||||
|     endsWith, |     endsWith, | ||||||
|     padStart, |     padStart, | ||||||
|     formatUrl, |     formatUrl, | ||||||
|  |     escapeHtml, | ||||||
|     // variables |     // variables | ||||||
|     g as global, |     g as global, | ||||||
|     isPositive, |     isPositive, | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								lib/utility/strings.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								lib/utility/strings.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -3,3 +3,4 @@ export function contains(s: string, key: string | any, ignoreCase?: boolean): bo | |||||||
| export function endsWith(s: string, suffix: string): boolean | export function endsWith(s: string, suffix: string): boolean | ||||||
| export function padStart(s: string, num: Number, char: string): boolean | export function padStart(s: string, num: Number, char: string): boolean | ||||||
| export function formatUrl(msg: string): string | export function formatUrl(msg: string): string | ||||||
|  | export function escapeHtml(text: string): string | ||||||
| @@ -54,6 +54,12 @@ | |||||||
|     <p> |     <p> | ||||||
|         把超链接解析替换为图标 |         把超链接解析替换为图标 | ||||||
|     </p> |     </p> | ||||||
|  |     <h2>escapeHtml</h2> | ||||||
|  |     <code>function escapeHtml(text: string): string</code> | ||||||
|  |     <h3>text: string</h3> | ||||||
|  |     <p> | ||||||
|  |         解析转换 html 代码为显示内容 | ||||||
|  |     </p> | ||||||
|     <hr /> |     <hr /> | ||||||
|     <h2>用法</h2> |     <h2>用法</h2> | ||||||
|     <pre>const util = window["lib-utility"]; |     <pre>const util = window["lib-utility"]; | ||||||
|   | |||||||
| @@ -34,7 +34,7 @@ function formatUrl(msg) { | |||||||
|     //const urlArrray = str.match(urlReg); |     //const urlArrray = str.match(urlReg); | ||||||
|     const p = /(http|ftp|https):\/\/.+?(\s|\r\n|\r|\n|\"|\'|\*|$)/g; |     const p = /(http|ftp|https):\/\/.+?(\s|\r\n|\r|\n|\"|\'|\*|$)/g; | ||||||
|     const r = msg.match(p); |     const r = msg.match(p); | ||||||
|     msg = htmlencode(msg); |     msg = escapeHtml(msg); | ||||||
|  |  | ||||||
|     if (r?.length > 0) { |     if (r?.length > 0) { | ||||||
|         const rs = []; |         const rs = []; | ||||||
| @@ -50,7 +50,17 @@ function formatUrl(msg) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return msg |     return msg; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function escapeHtml(text) { | ||||||
|  |     if (text == null) { | ||||||
|  |         return ''; | ||||||
|  |     } | ||||||
|  |     return String(text) | ||||||
|  |         .replaceAll('&', '&') | ||||||
|  |         .replaceAll('<', '<') | ||||||
|  |         .replaceAll('>', '>') | ||||||
|         .replaceAll('\r\n', '<br/>') |         .replaceAll('\r\n', '<br/>') | ||||||
|         .replaceAll('\n', '<br/>') |         .replaceAll('\n', '<br/>') | ||||||
|         .replaceAll('  ', ' '); |         .replaceAll('  ', ' '); | ||||||
| @@ -61,5 +71,6 @@ export { | |||||||
|     contains, |     contains, | ||||||
|     endsWith, |     endsWith, | ||||||
|     padStart, |     padStart, | ||||||
|     formatUrl |     formatUrl, | ||||||
|  |     escapeHtml | ||||||
| } | } | ||||||
		Reference in New Issue
	
	Block a user