diff --git a/views/assets/css/chat.css b/views/assets/css/chat.css index 85e8973..6a40573 100644 --- a/views/assets/css/chat.css +++ b/views/assets/css/chat.css @@ -35,7 +35,7 @@ word-wrap: break-word; /* Ensure long words don't overflow */ } .chat-container { - align-items: center; + align-items: flex-start; background-image: linear-gradient( to bottom, @@ -50,9 +50,9 @@ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); box-sizing: border-box; display: none; + flex: 1 0 auto; flex-direction: column; height: auto; - justify-content: space-between; overflow: hidden; padding: 0 0.5em; position: relative; @@ -61,21 +61,13 @@ .chat-member, .chat-user { align-items: center; - align-self: stretch; display: flex; + flex: 1 1 auto; flex-direction: row; flex-wrap: wrap; - justify-content: right; margin: 0.5em 0; - min-width: max-content; - overflow: visible; -} -.chat-member input, -.chat-user input { - background-color: #ffffff; /* White background color */ - border-radius: 12px; /* Rounded corners */ - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* Optional: adds a subtle shadow for depth */ - flex: 1; + max-height: 60%; + width: 100%; } .chat-member textarea, .chat-user textarea { @@ -83,16 +75,14 @@ border: 1px solid #ccc; /* Border color */ border-radius: 12px; /* Rounded corners */ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* Subtle shadow for depth */ - flex: 1; - flex-shrink: 0; - font-size: 1rem; /* Adjust font size */ - line-height: 1.5; /* Adjust line height for better readability */ - min-height: 100%; - /* max-height: 200px; /* Adjust this value as needed */ - margin: 6px 12px; /* Margin for space outside the container */ - overflow-y: auto; /* Adds a scrollbar when content exceeds max-height */ - padding: 8px; /* Padding for space inside the container */ - resize: vertical; /* Allows vertical resizing, none to disable */ + display: flex; + flex: 1 1 auto; + font-size: 1em; /* Adjust font size */ + line-height: 1.25; /* Adjust line height for better readability */ + margin: 0 1em; /* Margin for space outside the container */ + overflow: hidden; /* Hides the scrollbar */ + padding: 0.3em; /* Padding for space inside the container */ + resize: none; /* Allows vertical resizing, none to disable */ } .chat-refresh { position: absolute; /* Position relative to parent */ @@ -105,28 +95,28 @@ cursor: pointer; /* Pointer cursor on hover */ z-index: 2; /* Ensure it's above other content */ } -.chat-submit { +button.chat-submit { /* need specificity to overwrite button class */ flex: 0 0 auto; color: rgba(65, 84, 104, 0.85); background-color: rgba(183, 203, 225, 0.85); - margin: 0px; + margin: 0 0.5em; white-space: nowrap; /* Prevents text wrapping in the button */ } -.chat-submit:hover { +button.chat-submit:hover { background-color: rgba(195, 203, 52, 0.85); color: #061320; } -.chat-submit:focus { +button.chat-submit:focus { outline: none; /* Remove the default focus outline */ box-shadow: 0 0 8px 2px rgba(255, 255, 0, 0.6); /* Yellow glow */ } -.chat-submit:disabled { +button.chat-submit:disabled { background-color: rgba(63, 75, 83, 0.2); border-color: rgba(146, 157, 163, .7); color: rgba(146, 157, 163, .9); } .chat-system { - flex: 1 1 100%; + flex: 1 1 auto; flex-direction: column; height: 100%; justify-content: flex-start; @@ -168,11 +158,10 @@ opacity: 0; /* Max opacity of 50% */ } .label-spinner-container { - position: relative; display: flex; - align-items: center; - justify-content: center; /* Center contents horizontally */ - min-height: 50px; /* Ensure enough height for spinner and label */ + flex: 0 0 auto; + margin: 0 0.5em; + justify-content: flex-start; } .member-bubble, .user-bubble { background-color: rgba(225, 245, 254, 1); /* A light shade of blue for the user */ diff --git a/views/assets/css/main.css b/views/assets/css/main.css index fc9e70b..d99e8a8 100644 --- a/views/assets/css/main.css +++ b/views/assets/css/main.css @@ -196,11 +196,9 @@ body { border-radius: 22px; box-sizing: border-box; display: none; - flex: 1 1 66%; /* Use flex to assign 66% of the space */ + flex: 0 65%; height: auto; - max-width: 100%; - min-height: 50vh; - min-width: 66%; + min-height: 70vh; overflow: hidden; padding: 0px; } @@ -358,12 +356,12 @@ body { border: rgb(0, 25, 51, .3) 2px dotted; border-radius: 22px; display: none; - flex: 0 0 33%; /* Use flex to assign 33% of the space */ + flex: 0 auto; flex-direction: column; font-family: "Optima", "Segoe UI", "Candara", "Calibri", "Segoe", "Optima", Arial, sans-serif; font-size: 0.8em; height: fit-content; /* 100% will stretch to bottom */ - max-width: 33%; + max-width: 35%; padding: 0px; } .sidebar-widget { diff --git a/views/assets/css/members.css b/views/assets/css/members.css index 47ce18b..0eef32b 100644 --- a/views/assets/css/members.css +++ b/views/assets/css/members.css @@ -119,25 +119,4 @@ .checkbox-group label { padding-left: 0.5em; margin: 0; -} -/* media queries */ -@media screen and (min-width: 1024px) { - body { - margin-right: 3em; /* Increased margin for larger screens */ - max-width: 1024px; /* Set a max-width to center the content */ - margin-left: auto; /* Center the body horizontally */ - margin-right: auto; - } -} -@media screen and (max-width: 768px) { /* Adjust the max-width as needed */ - .page-column-collection { - flex-direction: column; - } - .main-content { - max-width: 100%; - } - .sidebar { - flex-basis: 100%; - max-width: 100%; - } } \ No newline at end of file diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs index f0e3341..ba4ec5f 100644 --- a/views/assets/js/bots.mjs +++ b/views/assets/js/bots.mjs @@ -347,27 +347,29 @@ function mOpenStatusDropdown(element){ } }) var content = element.querySelector('.bot-options') - console.log('mOpenStatusDropdown', content, element) if(content) content.classList.toggle('open') var dropdown = element.querySelector('.bot-options-dropdown') if(dropdown) dropdown.classList.toggle('open') } +/** + * Refresh collection on click. + * @param {Event} event - The event object. + * @returns {void} + */ async function mRefreshCollection(event){ const { id, } = event.target const type = id.split('-').pop() if(!mLibraries.includes(type)) throw new Error(`Library collection not implemented.`) const collection = await fetchCollections(type) + const collectionList = document.getElementById(`collection-list-${ type }`) + show(collectionList) /* no toggle, just show */ if(!collection.length) /* no items in collection */ return - const collectionList = document.getElementById(`collection-list-${ type }`) - if(!collectionList) - throw new Error(`Library collection list not found! Attempting element by id: "collection-list-${ type }".`) mUpdateCollection(type, collection, collectionList) event.target.addEventListener('click', mRefreshCollection, { once: true }) - return collection } /** * Set Bot data on server. diff --git a/views/assets/js/experience.mjs b/views/assets/js/experience.mjs index 3af8d1f..2ae999d 100644 --- a/views/assets/js/experience.mjs +++ b/views/assets/js/experience.mjs @@ -3,6 +3,7 @@ import { addMessageToColumn, assignElements, clearSystemChat, + escapeHtml, getInputValue, getSystemChat, hide, @@ -398,7 +399,7 @@ function mCreateCharacterDialog(){ dialogDiv.name = `dialog-${mEvent.id}` // set random type (max 3) const dialogType = Math.floor(Math.random() * 3) + 1 - dialogDiv.textContent = mEvent.dialog.dialog + dialogDiv.innerHTML = `

${ mEvent.dialog.dialog }

` dialogDiv.classList.add('char-dialog-box') dialogDiv.classList.add(`dialog-type-${dialogType}`) return dialogDiv diff --git a/views/assets/js/globals.mjs b/views/assets/js/globals.mjs index da44623..909eea5 100644 --- a/views/assets/js/globals.mjs +++ b/views/assets/js/globals.mjs @@ -33,6 +33,22 @@ class Globals { mLoginSelect.addEventListener('change', mSelectLoginId, { once: true }) } /* public functions */ + /** + * Escapes HTML characters in a string. + * @param {string} text - The text to escape. + * @returns {string} - The escaped text. + */ + escapeHtml(text){ + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + } + const escapedText = text.replace(/[&<>"']/g, m=>(map[m]) ) + return escapedText + } /** * Returns the avatar object if poplated by on-page EJS script. * @todo - refactor to api call @@ -184,7 +200,6 @@ function mHide(element, callbackFunction){ * @returns {boolean} - Whether the element is visible. */ function mIsVisible(classList){ - console.log('mIsVisible', classList) return classList.contains('show') } function mLogin(){ diff --git a/views/assets/js/guests.mjs b/views/assets/js/guests.mjs index 0dbbbef..03995fb 100644 --- a/views/assets/js/guests.mjs +++ b/views/assets/js/guests.mjs @@ -47,23 +47,6 @@ document.addEventListener('DOMContentLoaded', ()=>{ /* display page */ mShowPage() }) -/* public functions */ -/** - * Escapes HTML characters in a string. - * @param {string} text - The text to escape. - * @returns {string} - The escaped text. - */ -function escapeHtml(text) { - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - } - const escapedText = text.replace(/[&<>"']/g, m=>(map[m]) ) - return escapedText -} /* private functions */ /** * Adds a message to the chat column. @@ -89,7 +72,7 @@ function mAddMessage(message, options={ chatBubble.className = `chat-bubble ${bubbleClass}` mChatBubbleCount++ chatSystem.appendChild(chatBubble) - messageContent = escapeHtml(messageContent) + messageContent = mGlobals.escapeHtml(messageContent) if(typewrite){ let i = 0 let tempMessage = '' @@ -121,7 +104,7 @@ function mAddUserMessage(event){ // Dynamically get the current message element (input or textarea) let userMessage = chatInput.value.trim() if (!userMessage.length) return - userMessage = escapeHtml(userMessage) // Escape the user message + userMessage = mGlobals.escapeHtml(userMessage) // Escape the user message mSubmitInput(event, userMessage) mAddMessage({ message: userMessage }, { bubbleClass: 'user-bubble', @@ -313,8 +296,4 @@ function mToggleButton(event){ chatSubmit.disabled = true chatSubmit.style.cursor = 'not-allowed' } -} -/* exports */ -export { - escapeHtml, } \ No newline at end of file diff --git a/views/assets/js/members.mjs b/views/assets/js/members.mjs index 5167dcf..3c0a53e 100644 --- a/views/assets/js/members.mjs +++ b/views/assets/js/members.mjs @@ -76,7 +76,7 @@ function addMessageToColumn(message, options={ _delay: 10, _typewrite: true, }){ - let messageContent = message.message ?? message + const messageContent = message.message ?? message const { bubbleClass, _delay, @@ -85,7 +85,7 @@ function addMessageToColumn(message, options={ const chatBubble = document.createElement('div') chatBubble.id = `chat-bubble-${mChatBubbleCount}` chatBubble.classList.add('chat-bubble', bubbleClass) - chatBubble.innerHTML = messageContent + chatBubble.innerHTML = escapeHtml(messageContent) mChatBubbleCount++ systemChat.appendChild(chatBubble) } @@ -122,21 +122,8 @@ function clearSystemChat(){ // Remove all chat bubbles and experience chat-lanes under chat-system systemChat.innerHTML = '' } -/** - * Escapes HTML text. - * @public - * @param {string} text - The text to escape. - * @returns {string} - The escaped HTML text. - */ -function escapeHtml(text){ - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - return text.replace(/[&<>"']/g, function(m) { return map[m]; }); +function escapeHtml(text) { + return mGlobals.escapeHtml(text) } function getInputValue(){ return chatInputField.value.trim() @@ -353,11 +340,9 @@ function bot(_id){ * @returns {void} */ function mExperienceStart(event){ - event.preventDefault() - event.stopImmediatePropagation() - console.log('mExperienceStart()', mExperiences) mExperience = mExperiences[0] - stageTransition() + if(mExperience) + stageTransition() } function mFetchExperiences(){ return fetch('/members/experiences/') @@ -562,28 +547,14 @@ function toggleMemberInput(display=true, hidden=false){ if(hidden) hide(chatInput) } -// Function to toggle between textarea and input based on character count -function toggleInputTextarea(){ - const inputStyle = window.getComputedStyle(chatInput) - const inputFont = inputStyle.font - const textWidth = getTextWidth(chatInputField.value, inputFont) // no trim required - const inputWidth = chatInput.offsetWidth - /* pulse */ - clearTimeout(typingTimer); - spinner.style.display = 'none'; - mResetAnimation(spinner); // Reset animation - typingTimer = setTimeout(() => { - spinner.style.display = 'block'; - mResetAnimation(spinner); // Restart animation - }, 2000) - const listenerFunction = toggleInputTextarea - if (textWidth>inputWidth && chatInputField.tagName!=='TEXTAREA') { // Expand to textarea - chatInputField = replaceElement(chatInputField, 'textarea', true, 'input', listenerFunction) - focusAndSetCursor(chatInputField); - } else if (textWidth<=inputWidth && chatInputField.tagName==='TEXTAREA' ) { // Revert to input - chatInputField = replaceElement(chatInputField, 'input', true, 'input', listenerFunction) - focusAndSetCursor(chatInputField) - } +/** + * Toggles the input textarea. + * @param {Event} event - The event object. + * @returns {void} - The return is void. + */ +function toggleInputTextarea(event){ + chatInputField.style.height = 'auto' // Reset height to shrink if text is removed + chatInputField.style.height = chatInputField.scrollHeight + 'px' // Set height based on content toggleSubmitButtonState() } function toggleSubmitButtonState() { @@ -611,6 +582,7 @@ export { assignElements, availableExperiences, clearSystemChat, + escapeHtml, getInputValue, getSystemChat, hide, diff --git a/views/members.html b/views/members.html index d326ed6..008495b 100644 --- a/views/members.html +++ b/views/members.html @@ -11,7 +11,7 @@
chat
- +