forked from lavender/watch-party
		
	ui and emoji changes
This commit is contained in:
		
							parent
							
								
									362c990d22
								
							
						
					
					
						commit
						e6699e05dd
					
				
					 7 changed files with 249 additions and 119 deletions
				
			
		|  | @ -43,7 +43,8 @@ | ||||||
|         <button id="join-session-button">Join</button> |         <button id="join-session-button">Join</button> | ||||||
| 
 | 
 | ||||||
|         <p> |         <p> | ||||||
|           No session to join? <a href="/create.html">Create a session</a> instead. |           No session to join? | ||||||
|  |           <a href="/create.html">Create a session</a> instead. | ||||||
|         </p> |         </p> | ||||||
|       </form> |       </form> | ||||||
|     </div> |     </div> | ||||||
|  | @ -53,8 +54,13 @@ | ||||||
|       <div id="viewer-list"></div> |       <div id="viewer-list"></div> | ||||||
|       <div id="chatbox"></div> |       <div id="chatbox"></div> | ||||||
|       <form id="chatbox-send"> |       <form id="chatbox-send"> | ||||||
|         <input type="text" placeholder="Message... (/help for commands)" list="emoji-autocomplete" /> |         <input | ||||||
|         <div id="emoji-autocomplete"></div> <!-- DO NOT ADD SPACING INSIDE THE TAG IT WILL BREAK THE CSS kthxbye --> |           type="text" | ||||||
|  |           placeholder="Message... (/help for commands)" | ||||||
|  |           list="emoji-autocomplete" | ||||||
|  |         /> | ||||||
|  |         <div id="emoji-autocomplete"></div> | ||||||
|  |         <!-- DO NOT ADD SPACING INSIDE THE TAG IT WILL BREAK THE CSS kthxbye --> | ||||||
|       </form> |       </form> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,23 @@ | ||||||
| import { setDebounce, setVideoTime, setPlaying } from "./watch-session.mjs?v=9"; | import { setDebounce, setVideoTime, setPlaying } from "./watch-session.mjs?v=9"; | ||||||
| import { emojify, emojis } from "./emojis.mjs?v=9"; | import { emojify, emojis } from "./emojis.mjs?v=9"; | ||||||
| 
 | 
 | ||||||
|  | function insertAtCursor(input, textToInsert) { | ||||||
|  |   const isSuccess = document.execCommand("insertText", false, textToInsert); | ||||||
|  | 
 | ||||||
|  |   // Firefox (non-standard method)
 | ||||||
|  |   if (!isSuccess && typeof input.setRangeText === "function") { | ||||||
|  |     const start = input.selectionStart; | ||||||
|  |     input.setRangeText(textToInsert); | ||||||
|  |     // update cursor to be at the end of insertion
 | ||||||
|  |     input.selectionStart = input.selectionEnd = start + textToInsert.length; | ||||||
|  | 
 | ||||||
|  |     // Notify any possible listeners of the change
 | ||||||
|  |     const e = document.createEvent("UIEvent"); | ||||||
|  |     e.initEvent("input", true, false); | ||||||
|  |     input.dispatchEvent(e); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const setupChatboxEvents = (socket) => { | const setupChatboxEvents = (socket) => { | ||||||
|   // clear events by just reconstructing the form
 |   // clear events by just reconstructing the form
 | ||||||
|   const oldChatForm = document.querySelector("#chatbox-send"); |   const oldChatForm = document.querySelector("#chatbox-send"); | ||||||
|  | @ -11,25 +28,77 @@ const setupChatboxEvents = (socket) => { | ||||||
| 
 | 
 | ||||||
|   let autocompleting = false; |   let autocompleting = false; | ||||||
| 
 | 
 | ||||||
|   const replaceMessage = message => () => { |   const replaceMessage = (message) => () => { | ||||||
|     messageInput.value = message; |     messageInput.value = message; | ||||||
| 	autocomplete(); |     autocomplete(); | ||||||
|   } |   }; | ||||||
|   async function autocomplete(){ |   async function autocomplete() { | ||||||
|     if(autocompleting) return; |     if (autocompleting) return; | ||||||
| 	emojiAutocomplete.textContent = ""; |     emojiAutocomplete.textContent = ""; | ||||||
|     autocompleting = true; |     autocompleting = true; | ||||||
|     let text = messageInput.value.slice(0, messageInput.selectionStart); |     let text = messageInput.value.slice(0, messageInput.selectionStart); | ||||||
| 	const match = text.match(/(:[^\s:]+)?:[^\s:]*$/); |     const match = text.match(/(:[^\s:]+)?:([^\s:]*)$/); | ||||||
| 	if(!match || match[1]) return autocompleting = false; // We don't need to autocomplete.
 |     if (!match || match[1]) return (autocompleting = false); // We don't need to autocomplete.
 | ||||||
| 	const prefix = text.slice(0, match.index); |     const prefix = text.slice(0, match.index); | ||||||
| 	const search = text.slice(match.index + 1); |     const search = text.slice(match.index + 1); | ||||||
| 	const suffix = messageInput.value.slice(messageInput.selectionStart); |     const suffix = messageInput.value.slice(messageInput.selectionStart); | ||||||
| 	emojiAutocomplete.append(...(await emojis).filter(e => e.toLowerCase().startsWith(search.toLowerCase())).map(e => Object.assign(document.createElement("button"), {className: "emoji-option", textContent: e, onclick: replaceMessage(prefix + ":" + e + ":" + suffix)}))) |     const select = (button) => { | ||||||
| 	autocompleting = false; |       const selected = document.querySelector(".emoji-option.selected"); | ||||||
|  |       if (selected) selected.classList.remove("selected"); | ||||||
|  |       button.classList.add("selected"); | ||||||
|  |     }; | ||||||
|  |     emojiAutocomplete.append( | ||||||
|  |       ...(await emojis) | ||||||
|  |         .filter((e) => e.toLowerCase().startsWith(search.toLowerCase())) | ||||||
|  |         .map((name, i) => { | ||||||
|  |           const button = Object.assign(document.createElement("button"), { | ||||||
|  |             className: "emoji-option" + (i === 0 ? " selected" : ""), | ||||||
|  |             onmousedown: (e) => e.preventDefault(), | ||||||
|  |             onmouseup: () => | ||||||
|  |               insertAtCursor(button, name.slice(match[2].length) + ": "), | ||||||
|  |             onmouseover: () => select(button), | ||||||
|  |             onfocus: () => select(button), | ||||||
|  |           }); | ||||||
|  |           button.append( | ||||||
|  |             Object.assign(new Image(), { | ||||||
|  |               loading: "lazy", | ||||||
|  |               src: `/emojis/${name}.png`, | ||||||
|  |               className: "emoji", | ||||||
|  |             }), | ||||||
|  |             Object.assign(document.createElement("span"), { textContent: name }) | ||||||
|  |           ); | ||||||
|  |           return button; | ||||||
|  |         }) | ||||||
|  |     ); | ||||||
|  |     if (emojiAutocomplete.children[0]) | ||||||
|  |       emojiAutocomplete.children[0].scrollIntoView(); | ||||||
|  |     autocompleting = false; | ||||||
|   } |   } | ||||||
|   messageInput.addEventListener("input", autocomplete) |   messageInput.addEventListener("input", autocomplete); | ||||||
|   messageInput.addEventListener("selectionchange", autocomplete); |   messageInput.addEventListener("selectionchange", autocomplete); | ||||||
|  |   messageInput.addEventListener("keydown", (event) => { | ||||||
|  |     if (event.key == "ArrowUp" || event.key == "ArrowDown") { | ||||||
|  |       let selected = document.querySelector(".emoji-option.selected"); | ||||||
|  |       if (!selected) return; | ||||||
|  |       event.preventDefault(); | ||||||
|  |       selected.classList.remove("selected"); | ||||||
|  |       selected = | ||||||
|  |         event.key == "ArrowDown" | ||||||
|  |           ? selected.nextElementSibling || selected.parentElement.children[0] | ||||||
|  |           : selected.previousElementSibling || | ||||||
|  |             selected.parentElement.children[ | ||||||
|  |               selected.parentElement.children.length - 1 | ||||||
|  |             ]; | ||||||
|  |       selected.classList.add("selected"); | ||||||
|  |       selected.scrollIntoView({ scrollMode: "if-needed", block: "nearest" }); | ||||||
|  |     } | ||||||
|  |     if (event.key == "Tab") { | ||||||
|  |       let selected = document.querySelector(".emoji-option.selected"); | ||||||
|  |       if (!selected) return; | ||||||
|  |       event.preventDefault(); | ||||||
|  |       selected.onmouseup(); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
| 
 | 
 | ||||||
|   chatForm.addEventListener("submit", async (e) => { |   chatForm.addEventListener("submit", async (e) => { | ||||||
|     e.preventDefault(); |     e.preventDefault(); | ||||||
|  | @ -81,7 +150,12 @@ const setupChatboxEvents = (socket) => { | ||||||
|               " <code>/ping [message]</code> - ping all viewers<br>" + |               " <code>/ping [message]</code> - ping all viewers<br>" + | ||||||
|               " <code>/sync</code> - resyncs you with other viewers"; |               " <code>/sync</code> - resyncs you with other viewers"; | ||||||
| 
 | 
 | ||||||
|             printChatMessage("command-message", "/help", "b57fdc", helpMessageContent); |             printChatMessage( | ||||||
|  |               "command-message", | ||||||
|  |               "/help", | ||||||
|  |               "b57fdc", | ||||||
|  |               helpMessageContent | ||||||
|  |             ); | ||||||
|             handled = true; |             handled = true; | ||||||
|             break; |             break; | ||||||
|           default: |           default: | ||||||
|  | @ -114,8 +188,7 @@ export const setupChat = async (socket) => { | ||||||
|   window.addEventListener("keydown", (event) => { |   window.addEventListener("keydown", (event) => { | ||||||
|     try { |     try { | ||||||
|       const isSelectionEmpty = window.getSelection().toString().length === 0; |       const isSelectionEmpty = window.getSelection().toString().length === 0; | ||||||
|       if (event.code.match(/Key\w/) && isSelectionEmpty) |       if (event.code.match(/Key\w/) && isSelectionEmpty) messageInput.focus(); | ||||||
|         messageInput.focus(); |  | ||||||
|     } catch (_err) {} |     } catch (_err) {} | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -2,11 +2,20 @@ export function emojify(text) { | ||||||
|   let last = 0; |   let last = 0; | ||||||
|   let nodes = []; |   let nodes = []; | ||||||
|   text.replace(/:([^\s:]+):/g, (match, name, index) => { |   text.replace(/:([^\s:]+):/g, (match, name, index) => { | ||||||
|     if(last <= index) nodes.push(document.createTextNode(text.slice(last, index))) |     if (last <= index) | ||||||
|     nodes.push(Object.assign(new Image(), {src: `/emojis/${name}.png`, className: "emoji", alt: name})) |       nodes.push(document.createTextNode(text.slice(last, index))); | ||||||
|     last = index + match.length |     nodes.push( | ||||||
|   }) |       Object.assign(new Image(), { | ||||||
|   if(last < text.length) nodes.push(document.createTextNode(text.slice(last))) |         src: `/emojis/${name}.png`, | ||||||
|   return nodes |         className: "emoji", | ||||||
|  |         alt: name, | ||||||
|  |       }) | ||||||
|  |     ); | ||||||
|  |     last = index + match.length; | ||||||
|  |   }); | ||||||
|  |   if (last < text.length) nodes.push(document.createTextNode(text.slice(last))); | ||||||
|  |   return nodes; | ||||||
| } | } | ||||||
| export const emojis = Promise.resolve(["blobcat", "blobhaj"]) | export const emojis = fetch("/emojis") | ||||||
|  |   .then((e) => e.json()) | ||||||
|  |   .then((e) => e.map((e) => e.slice(0, -4))); | ||||||
|  |  | ||||||
|  | @ -80,10 +80,14 @@ export const setupJoinSessionForm = () => { | ||||||
|     saveNickname(nickname); |     saveNickname(nickname); | ||||||
|     saveColour(colour); |     saveColour(colour); | ||||||
|     try { |     try { | ||||||
|       await joinSession(nickname.value, sessionId.value, colour.value.replace(/^#/, "")); |       await joinSession( | ||||||
|  |         nickname.value, | ||||||
|  |         sessionId.value, | ||||||
|  |         colour.value.replace(/^#/, "") | ||||||
|  |       ); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       alert(e.message) |       alert(e.message); | ||||||
| 	  button.disabled = false; |       button.disabled = false; | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,59 +1,65 @@ | ||||||
| export default class ReconnectingWebSocket { | export default class ReconnectingWebSocket { | ||||||
|   constructor(url){ |   constructor(url) { | ||||||
|     if(url instanceof URL) { |     if (url instanceof URL) { | ||||||
|       this.url = url; |       this.url = url; | ||||||
|     } else { |     } else { | ||||||
|       this.url = new URL(url) |       this.url = new URL(url); | ||||||
|     } |     } | ||||||
|     this.connected = false; |     this.connected = false; | ||||||
|     this._eventTarget = new EventTarget(); |     this._eventTarget = new EventTarget(); | ||||||
|     this._backoff = 250; // milliseconds, doubled before use
 |     this._backoff = 250; // milliseconds, doubled before use
 | ||||||
| 	this._lastConnect = 0; |     this._lastConnect = 0; | ||||||
|     this._socket = null; |     this._socket = null; | ||||||
|     this._unsent = []; |     this._unsent = []; | ||||||
|     this._connect(true); |     this._connect(true); | ||||||
|   } |   } | ||||||
|   _connect(first) { |   _connect(first) { | ||||||
|     if(this._socket) try { this._socket.close() } catch (e) {}; |     if (this._socket) | ||||||
|  |       try { | ||||||
|  |         this._socket.close(); | ||||||
|  |       } catch (e) {} | ||||||
|     try { |     try { | ||||||
|       this._socket = new WebSocket(this.url.href); |       this._socket = new WebSocket(this.url.href); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       this._reconnecting = false; |       this._reconnecting = false; | ||||||
|       return this._reconnect() |       return this._reconnect(); | ||||||
|     } |     } | ||||||
|     this._socket.addEventListener("close", () => this._reconnect()) |     this._socket.addEventListener("close", () => this._reconnect()); | ||||||
|     this._socket.addEventListener("error", () => this._reconnect()) |     this._socket.addEventListener("error", () => this._reconnect()); | ||||||
|     this._socket.addEventListener("message", ({data}) => this._eventTarget.dispatchEvent(new MessageEvent("message", {data}))) |     this._socket.addEventListener("message", ({ data }) => | ||||||
|     this._socket.addEventListener("open", e => { |       this._eventTarget.dispatchEvent(new MessageEvent("message", { data })) | ||||||
|       if(first) this._eventTarget.dispatchEvent(new Event("open")); |     ); | ||||||
|       if(this._reconnecting) this._eventTarget.dispatchEvent(new Event("reconnected")); |     this._socket.addEventListener("open", (e) => { | ||||||
|  |       if (first) this._eventTarget.dispatchEvent(new Event("open")); | ||||||
|  |       if (this._reconnecting) | ||||||
|  |         this._eventTarget.dispatchEvent(new Event("reconnected")); | ||||||
|       this._reconnecting = false; |       this._reconnecting = false; | ||||||
|       this._backoff = 250; |       this._backoff = 250; | ||||||
|       this.connected = true; |       this.connected = true; | ||||||
|       while(this._unsent.length > 0) this._socket.send(this._unsent.shift()) |       while (this._unsent.length > 0) this._socket.send(this._unsent.shift()); | ||||||
|     }) |     }); | ||||||
|   } |   } | ||||||
|   _reconnect(){ |   _reconnect() { | ||||||
|     if(this._reconnecting) return; |     if (this._reconnecting) return; | ||||||
|     this._eventTarget.dispatchEvent(new Event("reconnecting")); |     this._eventTarget.dispatchEvent(new Event("reconnecting")); | ||||||
|     this._reconnecting = true; |     this._reconnecting = true; | ||||||
|     this.connected = false; |     this.connected = false; | ||||||
|     this._backoff *= 2; // exponential backoff
 |     this._backoff *= 2; // exponential backoff
 | ||||||
|     setTimeout(() => { |     setTimeout(() => { | ||||||
|       this._connect(); |       this._connect(); | ||||||
|     }, Math.floor(this._backoff+(Math.random()*this._backoff*0.25)-(this._backoff*0.125))) |     }, Math.floor(this._backoff + Math.random() * this._backoff * 0.25 - this._backoff * 0.125)); | ||||||
|   } |   } | ||||||
|   send(message) { |   send(message) { | ||||||
|     if(this.connected) { |     if (this.connected) { | ||||||
|       this._socket.send(message); |       this._socket.send(message); | ||||||
|     } else { |     } else { | ||||||
|       this._unsent.push(message); |       this._unsent.push(message); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   addEventListener(...a) { |   addEventListener(...a) { | ||||||
|     return this._eventTarget.addEventListener(...a) |     return this._eventTarget.addEventListener(...a); | ||||||
|   } |   } | ||||||
|   removeEventListener(...a) { |   removeEventListener(...a) { | ||||||
|     return this._eventTarget.removeEventListener(...a) |     return this._eventTarget.removeEventListener(...a); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { setupVideo } from "./video.mjs?v=9"; | import { setupVideo } from "./video.mjs?v=9"; | ||||||
| import { setupChat, logEventToChat, updateViewerList } from "./chat.mjs?v=9"; | import { setupChat, logEventToChat, updateViewerList } from "./chat.mjs?v=9"; | ||||||
| import ReconnectingWebSocket from "./reconnecting-web-socket.mjs" | import ReconnectingWebSocket from "./reconnecting-web-socket.mjs"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {string} sessionId |  * @param {string} sessionId | ||||||
|  | @ -169,7 +169,9 @@ const setupOutgoingEvents = (video, socket) => { | ||||||
|  */ |  */ | ||||||
| export const joinSession = async (nickname, sessionId, colour) => { | export const joinSession = async (nickname, sessionId, colour) => { | ||||||
|   // try { // we are handling errors in the join form.
 |   // try { // we are handling errors in the join form.
 | ||||||
|   const genericConnectionError = new Error("There was an issue getting the session information."); |   const genericConnectionError = new Error( | ||||||
|  |     "There was an issue getting the session information." | ||||||
|  |   ); | ||||||
|   window.location.hash = sessionId; |   window.location.hash = sessionId; | ||||||
|   let response, video_url, subtitle_tracks, current_time_ms, is_playing; |   let response, video_url, subtitle_tracks, current_time_ms, is_playing; | ||||||
|   try { |   try { | ||||||
|  | @ -178,19 +180,20 @@ export const joinSession = async (nickname, sessionId, colour) => { | ||||||
|     console.error(e); |     console.error(e); | ||||||
|     throw genericConnectionError; |     throw genericConnectionError; | ||||||
|   } |   } | ||||||
|   if(!response.ok) { |   if (!response.ok) { | ||||||
|     let error; |     let error; | ||||||
|     try { |     try { | ||||||
|       ({ error } = await response.json()); |       ({ error } = await response.json()); | ||||||
|       if(!error) throw new Error(); |       if (!error) throw new Error(); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       console.error(e); |       console.error(e); | ||||||
|       throw genericConnectionError; |       throw genericConnectionError; | ||||||
|     } |     } | ||||||
|     throw new Error(error) |     throw new Error(error); | ||||||
|   } |   } | ||||||
|   try { |   try { | ||||||
|     ({ video_url, subtitle_tracks, current_time_ms, is_playing } = await response.json()); |     ({ video_url, subtitle_tracks, current_time_ms, is_playing } = | ||||||
|  |       await response.json()); | ||||||
|   } catch (e) { |   } catch (e) { | ||||||
|     console.error(e); |     console.error(e); | ||||||
|     throw genericConnectionError; |     throw genericConnectionError; | ||||||
|  | @ -216,10 +219,10 @@ export const joinSession = async (nickname, sessionId, colour) => { | ||||||
|     setupIncomingEvents(video, socket); |     setupIncomingEvents(video, socket); | ||||||
|     setupChat(socket); |     setupChat(socket); | ||||||
|   }); |   }); | ||||||
|   socket.addEventListener("reconnecting", e => { |   socket.addEventListener("reconnecting", (e) => { | ||||||
|     console.log("Reconnecting..."); |     console.log("Reconnecting..."); | ||||||
|   }); |   }); | ||||||
|   socket.addEventListener("reconnected", e => { |   socket.addEventListener("reconnected", (e) => { | ||||||
|     console.log("Reconnected."); |     console.log("Reconnected."); | ||||||
|   }); |   }); | ||||||
|   //} catch (e) {
 |   //} catch (e) {
 | ||||||
|  |  | ||||||
|  | @ -12,8 +12,14 @@ | ||||||
|   --accent: rgb(var(--accent-rgb)); |   --accent: rgb(var(--accent-rgb)); | ||||||
|   --fg-transparent: rgba(var(--fg-rgb), 0.125); |   --fg-transparent: rgba(var(--fg-rgb), 0.125); | ||||||
|   --bg-transparent: rgba(var(--bg-rgb), 0.125); |   --bg-transparent: rgba(var(--bg-rgb), 0.125); | ||||||
|   --chat-bg: linear-gradient(var(--fg-transparent), var(--fg-transparent)), linear-gradient(var(--bg), var(--bg)); |   --chat-bg: linear-gradient(var(--fg-transparent), var(--fg-transparent)), | ||||||
|   --autocomplete-bg: linear-gradient(var(--fg-transparent), var(--fg-transparent)), linear-gradient(var(--fg-transparent), var(--fg-transparent)), linear-gradient(var(--bg), var(--bg)); |     linear-gradient(var(--bg), var(--bg)); | ||||||
|  |   --autocomplete-bg: linear-gradient( | ||||||
|  |       var(--fg-transparent), | ||||||
|  |       var(--fg-transparent) | ||||||
|  |     ), | ||||||
|  |     linear-gradient(var(--fg-transparent), var(--fg-transparent)), | ||||||
|  |     linear-gradient(var(--bg), var(--bg)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| html { | html { | ||||||
|  | @ -158,13 +164,19 @@ button.small-button { | ||||||
|   overflow-wrap: break-word; |   overflow-wrap: break-word; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .chat-message > strong, #viewer-list strong { | .chat-message > strong, | ||||||
|  | #viewer-list strong { | ||||||
|   color: var(--user-color, var(--default-user-color)); |   color: var(--user-color, var(--default-user-color)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @supports (-webkit-background-clip: text) { | @supports (-webkit-background-clip: text) { | ||||||
|   .chat-message > strong, #viewer-list strong { |   .chat-message > strong, | ||||||
|     background: linear-gradient(var(--fg-transparent), var(--fg-transparent)), linear-gradient(var(--user-color, var(--default-user-color)), var(--user-color, var(--default-user-color))); |   #viewer-list strong { | ||||||
|  |     background: linear-gradient(var(--fg-transparent), var(--fg-transparent)), | ||||||
|  |       linear-gradient( | ||||||
|  |         var(--user-color, var(--default-user-color)), | ||||||
|  |         var(--user-color, var(--default-user-color)) | ||||||
|  |       ); | ||||||
|     -webkit-background-clip: text; |     -webkit-background-clip: text; | ||||||
|     color: transparent !important; |     color: transparent !important; | ||||||
|   } |   } | ||||||
|  | @ -183,7 +195,7 @@ button.small-button { | ||||||
|   font-size: 0.85em; |   font-size: 0.85em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .chat-message.command-message{ | .chat-message.command-message { | ||||||
|   font-size: 0.85em; |   font-size: 0.85em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -240,9 +252,11 @@ button.small-button { | ||||||
|   position: absolute; |   position: absolute; | ||||||
|   bottom: 3.25rem; |   bottom: 3.25rem; | ||||||
|   background-image: var(--autocomplete-bg); |   background-image: var(--autocomplete-bg); | ||||||
|   padding: 0.25rem; |  | ||||||
|   border-radius: 6px; |   border-radius: 6px; | ||||||
|   width: calc(100% - 4.5rem); |   width: calc(100% - 4.5rem); | ||||||
|  |   max-height: 8.5rem; | ||||||
|  |   overflow-y: auto; | ||||||
|  |   clip-path: inset(0 0 0 0 round 8px); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #emoji-autocomplete:empty { | #emoji-autocomplete:empty { | ||||||
|  | @ -253,17 +267,29 @@ button.small-button { | ||||||
|   background: transparent; |   background: transparent; | ||||||
|   font-size: 0.75rem; |   font-size: 0.75rem; | ||||||
|   text-align: left; |   text-align: left; | ||||||
|   margin: 0 0 0.25rem; |   margin: 0 0.25rem; | ||||||
|   border-radius: 4px; |   border-radius: 4px; | ||||||
|   width: 100%; |   width: calc(100% - 0.5rem); | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   padding: 0.25rem 0.5rem; | ||||||
|  |   scroll-margin: 0.25rem; | ||||||
| } | } | ||||||
| 
 | .emoji-option:first-child { | ||||||
| .emoji-option:hover, .emoji-option:focus { |   margin-top: 0.25rem; | ||||||
|   background: var(--fg-transparent); |  | ||||||
| } | } | ||||||
| 
 |  | ||||||
| .emoji-option:last-child { | .emoji-option:last-child { | ||||||
|   margin: 0; |   margin-bottom: 0.25rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .emoji-option .emoji { | ||||||
|  |   width: 1.25rem; | ||||||
|  |   height: 1.25rem; | ||||||
|  |   margin: 0 0.5rem 0 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .emoji-option.selected { | ||||||
|  |   background: var(--fg-transparent); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #join-session-colour { | #join-session-colour { | ||||||
|  | @ -280,7 +306,10 @@ button.small-button { | ||||||
|   cursor: pointer; |   cursor: pointer; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| input[type="color"]::-moz-color-swatch, input[type="color"]::-webkit-color-swatch, input[type="color"]::-webkit-color-swatch-wrapper { /* This *should* be working in Chrome, but it doesn't for reasons that are beyond me. */ | input[type="color"]::-moz-color-swatch, | ||||||
|  | input[type="color"]::-webkit-color-swatch, | ||||||
|  | input[type="color"]::-webkit-color-swatch-wrapper { | ||||||
|  |   /* This *should* be working in Chrome, but it doesn't for reasons that are beyond me. */ | ||||||
|   border: none; |   border: none; | ||||||
|   margin: 0; |   margin: 0; | ||||||
|   padding: 0; |   padding: 0; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue